Skip to content

Design notes (Java)

Martin Desruisseaux edited this page Oct 13, 2022 · 4 revisions

How unions are represented

Some structures in OGC/ISO standards are defined as unions instead than interface. There is no direct equivalent for unions in the Java language. GeoAPI represents unions as ordinary interfaces with a documentation saying (usually) that exactly one method shall return a non-null or non-empty value. While only one value is allowed in theory, in some cases like ParameterValue more than one method can provide a value if those methods provide the same information in different forms (e.g. a numerical value as integer or as floating point).

Why wildcard bounds in collections

Many properties defined in ISO standards allow multiple values. Those properties are represented by collections in Java interfaces. GeoAPI uses wildcard bounds (e.g. Collection<? extends T>) for many of them. The reasons are:

  • We sometime need to override a property in a sub-interface with a different type of elements in the collection, in order to comply to some restriction documented in OGC/ISO standards. This is possible only if the collection use wildcard bounds.
  • For making easier to use frameworks like JAXB or Hibernate. Those frameworks require implementation classes, for example Collection<MyImplementationOfT>. Using wildcard bounds make that possible.

Why Optional<T> constructs are not used

For each property, ISO 19111 and 19115 standards specifies if the property is optional, mandatory or conditional. Since Java 8, optional properties of type T can be represented as Optional<T> in getter method signatures. However GeoAPI does not use this construct for the following reasons:

  • Backward compatibility with GeoAPI 3.0, which was defined before Java 8.
  • The getter methods in org.opengis.metadata packages can be used with the JavaBeans pattern. While GeoAPI does not define the corresponding setter methods (this is left to implementors), the interfaces were designed with that pattern in mind. JavaBeans require getter return type to be the same than setter argument type.
  • Frameworks like JAXB depend on above-cited JavaBeans pattern and are not (at the time of writing) compatible with Optional.
  • ISO specifications also have a "conditional" obligation level in addition of "mandatory" and "optional". We can map "mandatory" to T and "optional" to Optional<T>, but we can not easily choose whether "conditional" should map to T or Optional<T>; this is a gray area.
  • Obligation level sometime changes between different versions of ISO standards. If getter return type reflects the obligation level, it would add one more risk of incompatible changes in future GeoAPI versions.
  • Wildcard bounds like Optional<? extends T> makes optional verbose. By contrast a plain T return type is implicitly covariant.

Which interface methods have default body

Since Java 8, it is possible to provide a default implementation for methods in interfaces. Without default methods, implementors are forced to provide an implementation for all methods enumerated in an interface, including the optional ones, even when they are irrelevant to their needs. Default methods can reduce this pain. The policy about whether to provide a default method or not is as below, in order (the first matching criterion apply):

  1. All deprecated properties, no matter their obligation level, have default methods delegating their computation to the non-deprecated methods.
  2. Mandatory properties have no default methods. Implementors are forced to provide an implementation.
  3. Optional properties have default methods typically implemented as return Collections.emptyList() or, if the property is not a collection, return null.
  4. Conditional properties in unions have default methods as if they were optional properties. The reason is that only one property in an union shall be provided and all other properties shall be left to null or emptyList.
  5. Other conditional properties may or may not have default methods, on a case-by-case basis. Some reasons for providing a default method are when a conditional property is closely related to an optional property (e.g. the unit of measurement of another property given as number), or when conditional properties in an interface are used in a way very similar to union (e.g. LegalConstraints). In the later case we left one method, the most frequently used one, without method body for attracting developer's attention on the fact that there is an pseudo-union. The following table lists the choices applied in GeoAPI:

Legend: ❌ means that no default implementation is provided - implementors have to define those methods. ✔️ means that a default implementation returning null or an empty collection is provided. This list omits unions and deprecated methods.

Interface Method Default
Metadata getLocalesAndCharsets()
getParentMetadata()
getMetadataScopes() ✔️
Identification getTopicCategories() ✔️
getExtents() ✔️
DataIdentification getLocalesAndCharsets()
ServiceIdentification getCouplingType() ✔️
getCoupledResources()
Extent getDescription()
getGeographicElements()
getVerticalElements()
getTemporalElements()
VerticalExtent getVerticalCRS() ✔️
Party getName() ✔️
Individual getPositionName()
Organisation getLogo()
LegalConstraints getAccessConstraints()
getUseConstraints()
getOtherConstraints()
FeatureCatalogueDescription getFeatureCatalogueCitations() ✔️
SampleDimension getUnits()
Distribution getDistributionFormats() ✔️
Distributor getDistributorFormats() ✔️
Medium getDensityUnits()
Lineage getStatement() ✔️
getProcessSteps() ✔️
getSources() ✔️
Source getDescription() ✔️
getScope() ✔️
AssociatedResource getName() ✔️
getMetadataReference()
DataQuality getReports() ✔️
getLineage() ✔️
ExtendedElementInformation getObligation() ✔️
getCondition() ✔️
getMaximumOccurrence() ✔️
getDomainValue() ✔️
Georectified getCheckPointDescription() ✔️
Ellipsoid getSemiMinorAxis() ✔️
getInverseFlattening() ✔️
isIvfDefinitive() ✔️
PrimeMeridian getGreenwichLongitude() ✔️
CoordinateSystemAxis getRangeMeaning()
CoordinateOperation getSourceCRS() ✔️
getTargetCRS() ✔️
getVersion() ✔️