DevelopingTypes - gama-platform/gama GitHub Wiki

Developing Types

GAML has built-in data types (int, float, point, list, geometry, date...). A plugin can add new ones. For example, gama.extension.kml adds the kml type, gama.extension.image adds image, and gama.extension.dataframe adds dataframe. Once declared, a type can be used like any other type:

kml k <- kml(nil);            // casting operator generated automatically
list<kml> exports;            // as content type of a container

A new type needs two Java classes:

  1. the value class: the Java class of the values of the type (e.g. GamaKmlExport);
  2. the type class: a subclass of GamaType annotated with @type, which describes the type to GAML and converts other values into it (e.g. GamaKmlExportType).

The annotation processor (gama.processor) registers the type in the plugin's generated gaml/ folder (class gaml.additions.<shortcut>.GamlAdditions). Never edit that code by hand. See Developing Plugins for the plugin set-up.

classDiagram
    class GamaType~Support~ {
        <<abstract>>
        +cast(IScope, Object obj, Object param, boolean copy) Support
        +getDefault() Support
        +canCastToConst() boolean
        +deserializeFromJson(IScope, IMap) Support
    }
    class GamaKmlExportType {
        @type(name = "kml")
    }
    class GamaKmlExport
    GamaType <|-- GamaKmlExportType
    GamaKmlExportType ..> GamaKmlExport : wraps
Loading

The value class

The value class can be any Java class. The KML example uses a plain class (gama.extension.kml/src/gama/extension/kml/GamaKmlExport.java). However, a value class should implement gama.api.types.misc.IValue (in gama.api), so that GAMA can display, copy and save its values:

  • String stringValue(IScope scope): the text shown to the user;
  • IValue copy(IScope scope): a copy, used by copy(...) and by casts with copy = true;
  • IType<?> getGamlType(): the GAML type of the value;
  • String serializeToGaml(boolean includingBuiltIn): a GAML expression that rebuilds the value;
  • serializeToJson(IJson json): the JSON form of the value.

Attributes of the values

To let modelers write my_value.name, declare the attributes on the value class (or on its interface) with @vars/@variable and give each one a @getter (and optionally a @setter). For example, gama.api/src/gama/api/types/color/IColor.java declares the attributes of colours:

@vars ({ @variable (name = IKeyword.COLOR_RED, type = IType.INT, doc = { @doc ("Returns the red component of the color (between 0 and 255)") }),
         ... })
public interface IColor extends IValue, ... {

    @getter (IKeyword.COLOR_RED)
    default Integer red() { ... }
    ...
}

A getter on a value class has no parameter, or only an IScope. See @vars, @getter and @setter.

The type class

The type class extends gama.api.gaml.types.GamaType<Support>, where Support is the value class. It is annotated with @type. Here is the complete type class of kml (gama.extension.kml/src/gama/extension/kml/GamaKmlExportType.java):

@type (
        name = "kml",
        id = GamaKmlExportType.ID,
        wraps = { GamaKmlExport.class },
        kind = ISymbolKind.REGULAR,
        concept = { IConcept.TYPE },
        doc = { @doc ("Type of variables that enables to store objects and to export them into a KML (Keyhole Markup Language) file") })
public class GamaKmlExportType extends GamaType<GamaKmlExport> {

    static final int ID = IType.BEGINNING_OF_CUSTOM_TYPES + 29;

    public GamaKmlExportType(final ITypesManager typesManager) {
        super(typesManager);
    }

    @Override
    public boolean canCastToConst() { return true; }

    @Override
    @doc ("Returns a kml exportation object if the argument is alrady of type kml, otherwise nil")
    public GamaKmlExport cast(final IScope scope, final Object obj, final Object param, final boolean copy)
            throws GamaRuntimeException {
        if (obj instanceof GamaKmlExport) return (GamaKmlExport) obj;
        return null;
    }

    @Override
    public GamaKmlExport getDefault() { return new GamaKmlExport(); }
}

The processor checks that the class implements IType, and generates the code that creates it with new GamaKmlExportType(typesManager). The constructor taking an ITypesManager is therefore required.

The @type annotation

Attribute Default Use
name (required) The GAML keyword of the type.
id (required) A unique integer id (see below).
wraps (required) The Java classes of the values. The first one is the preferred one. GAMA uses these classes to find the GAML type of a Java value, for example the return type of an operator.
kind ISymbolKind.REGULAR REGULAR for most types, NUMBER for numeric types.
concept {} Keywords from IConcept, used by the documentation.
category {} Categories used by the documentation.
internal false Hides the type from the documentation.
doc {} The documentation of the type.

Choosing the id

The ids of the built-in types are constants of gama.api.gaml.types.IType (IType.INT = 1, IType.FLOAT = 2, IType.GEOMETRY = 13, IType.DATE = 23...). GAMA uses these ranges:

Range Used for
below 5000 built-in types of GAMA
from IType.BEGINNING_OF_CUSTOM_TYPES (5000) types defined in plugins
from IType.BEGINNING_OF_SPECIES_TYPES (10000) types created for species, numbered automatically
from IType.BEGINNING_OF_FILE_TYPES (20000) types created for @file declarations, numbered automatically

Define the id of your type as IType.BEGINNING_OF_CUSTOM_TYPES + n, with an n that is not used by another plugin. Search for BEGINNING_OF_CUSTOM_TYPES + in the workspace to see the values already taken (for example 29 for kml, 30 for image, 35 for dataframe). The old constant AVAILABLE_TYPE and the old advice to use ids above SPECIES_TYPE are obsolete.

Methods to implement

  • Support cast(IScope scope, Object obj, Object param, boolean copy) (abstract): converts any value into a value of the type. It returns obj itself if it is already of the type (or a copy if copy is true), builds a new value when a conversion makes sense, and returns null otherwise. param is an optional parameter of the cast. Document this method with @doc: every type automatically gets a casting operator with the name of the type (kml(x)), and that operator calls cast. The processor reports a warning if this documentation is missing.
  • Support getDefault(): the default value of variables of this type (null means nil).
  • boolean canCastToConst(): true if a cast of a constant can be computed at compile time.
  • Support deserializeFromJson(IScope scope, IMap<String, Object> map2) (optional): rebuilds a value from its JSON form, when the value class implements serializeToJson.

GamaType has other methods that you can override, for example to declare a content type or a key type for container-like types. Container types extend GamaContainerType instead (see gama.extension.dataframe/src/gama/extension/dataframe/GamaDataFrameType.java).

Operators on the new type

Operators can use the value class as parameter or return type: GAMA finds the GAML type through wraps. If the Java return type is not precise enough, set type = <your id> in @operator. You can also put @operator directly on methods of the value class: the value then becomes the first operand. See Developing Operators.

Creating a type with the developer tools

The GAMA developer tools provide the wizard GAMA > Create GAMA artefacts > New GAMA Type (also in File > New > Other... > GAMA Development). It "creates a new GAML type and the Java class of its values". It asks for the plugin, the package, the class name (default MyThingType) and the type name. It generates:

  • MyThingType extends GamaType<MyThing>, with the @type annotation, the constants NAME and ID = IType.BEGINNING_OF_CUSTOM_TYPES + <offset>, cast (with copy support), getDefault() (returning null), canCastToConst() (returning true) and deserializeFromJson;
  • MyThing implements IValue, with a name attribute declared with @vars and @getter("name").

The offset of the id is computed from the name of the type. If it collides with the id of another type, change it.

Alternative: GAML classes

If your values are structured objects with attributes and actions, you can declare a GAML class instead of a type: annotate a Java class implementing IObject with @gaml_class. Modelers can then create instances with my_class(...), use them as types, and even inherit from them in GAML with class ... parent: .... See Developing Species and the migration guide.

⚠️ **GitHub.com Fallback** ⚠️