-
Notifications
You must be signed in to change notification settings - Fork 38
Design notes (Java)
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).
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.
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" toOptional<T>
, but we can not easily choose whether "conditional" should map toT
orOptional<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 plainT
return type is implicitly covariant.
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):
- All deprecated properties, no matter their obligation level, have default methods delegating their computation to the non-deprecated methods.
- Mandatory properties have no default methods. Implementors are forced to provide an implementation.
- Optional properties have default methods typically implemented as
return Collections.emptyList()
or, if the property is not a collection,return null
. - 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
oremptyList
. - 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() |
✔️ |