Skip to end of metadata
Go to start of metadata

The vast benefit of these specifications is communicated via their included UML diagrams. We use this information to define the names of our interfaces, methods and fields.
Here is an example for us to work through.

Example "UML Diagram"

Figure A.10 defines metadata about the content of a coverage and the feature catalogue(s) used to define features. The data dictionary for this diagram is located in B.2.8.

        +-----------------------+
        | MD_ContentInformation |
        +-----------------------+
                     ^
                     |--------------------------------.
                     |                                |
+------------------------------------------+ +--------------------------------+
|            MD_CoverageDescription        | |   MD_FeatureCatalogDescription |
|------------------------------------------| |--------------------------------|
| + attributeDescription : RecordType      | | complianceCode [0.1] : Boolean |
| + contentType: MD_CoverageContentTypeCode| | ...                            |
|                                          | +--------------------------------+
+------------------------------------------+

      +----------<<CodeList>>------+
      | MD_CoverageContentTypeCode |
      |----------------------------|
      | + image                    |
      | + thematicClassification   |
      | + physicalMeasurement      |
      +----------------------------+

Figure A.10 - Content information

You can use this information to quickly define your interfaces and code lists. If you package starts getting crowded use these
diagram names as guides when defining sub packages.

A code list is similar in spirit to a Java 5 enum - but you can extended it with additional values later. Both constructs allow you to write well-defined machine readable text.

Mapping to Java

Here are some other well-known mappings you can follow to make everything consistent.

Optional Field

ISO

 

Java

Optional

Real

0:1

Double

Value may be null

Integer

0:1

Integer

Value may be null

Boolean

0:1

Boolean

Value may be null

As an example GeographicExtent.getInclusion() is an optional field.

Mandatory Field

ISO

 

Java

Mandatory

Real

1:1

Double

May be null if unknown, yes that is invalid

Integer

1:1

Integer

May be null if unknown, yes that is invalid

Boolean

1:1

Boolean

May be null if unknown

Invalid (or incomplete) information exist in the field. We use the boxed types (Integer, Double, Boolean) so we can return null to represent missing information. In addition, this approach protect us from ISO 19115 changes in OPTIONAL versus MANDATORY status of attributes.

Mandatory Field with a Default

ISO

 

Java

Mandatory with Default

Real

1:1

double

Indicate default value in javadocs

Integer

1:1

integer

Indicate default value in javadocs

Boolean

1:1

boolean

Indicate default value (true or false) in javadocs

Filter

1:1

Filter

Indicate default ( INCLUDES or EXCLUDES) in javadocs

Here are two examples:

  • SampleDimension.getOffset() returns an int.
  • FeatureType.isHidden() returns a boolean.

Mandatory Fields as Object Definition

When your manditory attribute completly defines your data structure you are forced to use primitives:

  • Interfaces that perform operations cannot function without their full definition, like MathTransform1D.transform(double). Most referencing and geometry interfaces use primitive types in this manner.
  • Interfaces that function as Data Objects cannot exist without their full definition, like RepresentativeFraction.getDenominator().

ISO

 

Java

Real

1:1

double

Integer

1:1

int

Boolean

1:1

boolean

Handling of Text

GeoAPI provides some additional data objects for representing text (beyond the usual String and Enum provided by Java). Please review the following table to see what is applicable for your class.

ISO

 

Java

Handling of Text

Example

Enum

0:1

Enum

A frozen set of machine readable text - Java 5 only

 

CodeList

0:1

org.opengis.util.CodeList

Extensible set of machine readable text

 

CharacterString

0:1

String

Free form machine readable text (for things like code and version)

 

CharacterString

0:1

org.opengis.util.InternationalString

Free form human readable text

 

Common Data Objects

ISO

 

Java

Common Data Objects

UnlimitedInteger

0:1

org.opengis.util.UnlimitedInteger

Integer with sentinel values for positive and negative Infinity

Multiplicity & Collections

ISO

 

Java

Comments

Real

0:N

List<Double>

 

Integer

0:N

List<Integer>

 

Point

0:N

PointList

Extends List<Point> but has accessors

ISO

 

Java

Collections

TransfiniteSet

 

org.opengis.geometry.TransfiniteSet

 

Collection

 

Collection<T>

Allow implementations to be specific

Set

 

Set<T>

 

Bag

 

Collection<T>

 

Sequence

 

List<T>

 

CircularSequence

 

none

We have no representation at this time

Dictionary

 

Map<K,V>

 

KeyValuePair

 

Map.Entry<K,V>

 

Tips and Tricks

Why use Objects rather then Primitives?

You may of noticed that we recomend using Objects more often then primitives - the simple reason is to use null to represent an unknown value.

Please note that even attributes marked as mandatory by the specification may have a null value at runtime. Either when the object is first being set up, or if a value is actually unknown.

Not filling in a mandatory value would make an instance incomplete or invalid (but we still need to let it exist). There are several problems with direct use of primitive types in such case:

Why are primitives so bad?

  1. An Invalid integer ends up being a "magic number"
    • documented in the javadocs like -1;
    • documented as a static final constant (like static final int UNKNOWN = -1);
    • Note: this problem does not apply to floating point values, since Double.NaN is a valid return value when the value is missing.
  2. The danger with this approach is applications are forced to avoid interfaces until they had valid content;
    • they end up using a Map until they have all of the required information (horrible!)
  3. Implementations end up throwing exceptions (when asked for data they do not have yet)

Working with partial content can be done, some specifications from the OGC are kind enough to provide "default" values in this case. Default values are often best captured as part of the javadocs for a factory interface, or even as static final values (if you are lucky enough to be working with a read-only interface).

Representing Data Objects

In a traditional programming language we would identify these objects using structs, in Java we encode them as Data Objects. The idea here is that the object is completly defined by its fields.

To define a Data Object in Java your interface needs to includes a definition of equals and hashcode (in the javadocs).

Here is an example - RepresentativeFraction is completly defined by its only field (the denominator):

BEFORE: A Normal Interface

To turn this into a Data Object we need to supply equals and hashcode contracts:

After: As a Data Object
Use of NullObjects - when null is not enough

This is some of the only time you will see classes GeoAPI. When information is unknown we would like to represent it as null. However the meaning of "null" may change depending on where in a specification it is used.

Consider the following example:

  • A Query with an unknown filter will fetch everything (all content will be returned by the query)
  • A Style with an unknown filter will fetch nothing (nothing will be drawn)

In these cases we can supply a NullObject (actually two of them - one for each meaning).

  • Filter.INCLUDES
  • Filter.EXCLUDES

The javadocs will end up refering to these constants:

Follow Java Naming Conventions

The usual Java naming convetions apply, use of CamelCase for classes methodNames and fieldNames. Use of UNDERSCORE_NAMING for ENUMERATIONS, and STATIC_FINALS (as appear in CodeLists).

Java Beans Naming Conventions for "Model" (ie Fields Access)

While mapping from UML field names please keep the java bean naming conventions in mind:

  • Integer getNumber()
  • void setNumber(Integer number)
  • Double getMeasure()
  • void setMeasure(Integer number)
  • Boolean isFlag()
  • void setFlag(Boolean flag)
  • List<Identifier> getIdentifiers()

Java Bean naming should be used for the "model" represented by the ISO specification.

Collections Naming Conventions for "Views" (ie Query or Derrived Information)

Collection naming should be used for the "pragmatic" needs born by our experience. They are used to make data access easier (in much the same way Map.keySet() and Map.values() represent alternate views on a ap).

Type Narrowing

Be specific with Type Narrowing and Javadocs

If a common set of assumptions can be named, go for it.

When we subclass we can use two tools to be more specific:

  • Using type narrowing we can be more specific about the return values
  • By providing javadocs (even for methods that have not changed in signature)
Be Intentional with Collections

In the above example we can only type narrow the Collection class (from Collection to List), we cannot type narrow the element class without getting deeper into Java generics.

Depending on what you do with collections and generics you are in for a world of hurt - the section on interface design will cover the trade-offs involved.

Labels
  • None