Slot-o-matic

Introduction

The Slot-o-matic tool is a gradle plugin that generates Java code for Baja slots based on class-level annotations on a Baja object. The generated code is placed in the same source file, and none of the other code is changed in any way

Usage

Invocation

Slot-o-matic is invoked by gradle in a similar way to how you compile your Java code:

gradlew :module-rt:slotomatic

This will process all Java files in your module that contain slot annotations and generate code for them. Slot-o-matic will compile any Java file that meets the following conditions:

  1. The file name is of the format B[A-Z]*.java, e.g. BObject.java or BSystem.java, but not Ball.java
  2. The top-level class in the file is annotated with at least one of the Niagara annotations detailed below.

By default, slot-o-matic will not compile files that have not changed since the last time the tool was run. This can be changed with properties as outlined below.

Slot-o-matic options

When invoking slot-o-matic on your project, there are system properties which can control the behavior of the tool. These can be defined when using gradle like so:

gradlew :module-rt:slotomatic -Dproperty.name(=value1,value2,valuen)

Force Recompile

gradlew :module-rt:slotomatic -Dslotomatic.forceRecompile

When run with this property set, slot-o-matic will recompile any files it comes across, even if they have not been changed since the last time slot-o-matic was run.

Include and Exclude

Slot-o-matic allows you include (or exclude) specific files, folders, classes, or packages when running. This is especially useful when dealing with a large codebase consisting of a mix of legacy and annotation-based slot code.

All include and excludes are relative to the source root of your module: src for code and srcTest for data.

For example, to only run slot-o-matic on one file:

gradlew :module-rt:slotomatic -Dslotomatic.include=com/acme/driver/BAcmeDriver.java
# or
gradlew :module-rt:slotomatic -Dslotomatic.include=com.acme.driver.BAcmeDriver

You can specify multiple files as well, and wildcards are supported:

gradlew :module-rt:slotomatic -Dslotomatic.include=com.acme.driver.BAcmeDriver,com.acme.driver.BAcmePoint
# Include all classes in com.acme.driver
gradlew :module-rt:slotomatic -Dslotomatic.include=com.acme.driver.*
gradlew :module-rt:slotomatic -Dslotomatic.include=com/acme/driver/*

Excludes work in the same way, but have the effect of re-slotting everything but files matching the exclude pattern

Niagara Annotations

Starting with Niagara 4, developers can use Java annotations on their Baja classes to indicate their slots and other information used by the Niagara runtime environment. All supported annotations and their usage are detailed below.

Useful notes

There are a few caveats/notes which apply to all Niagara annotations.

Java Annotation Types

There are three types of annotations defined by the Java language. You may see references to these different annotation types in this document:

String Escaping in Niagara Annotations

Unless otherwise noted, all attributes of “String” type must be fully-escaped String literals. You cannot use String constants, even if they are in the same class that is being annotated. Although this will appear to work, Slot-o-matic will not parse them correctly and you will end up with incorrect code.

If quotation marks are used in a String, They need to be escaped as such:

defaultValue="BString.make("Hello World")"      // will not work because the quotes can not be parsed
defaultValue="BString.make(\"Hello World\")"    // will work because the quotes have been escaped

Nested quotes must also be escaped properly:

defaultValue="BString.make(\"He said, \"Hello\" to her.\")"     // will not work
defaultValue="BString.make(\"He said, \\\"Hello\\\" to her.\")"   // will work

@NiagaraType

@NiagaraType(
  agent = {
    @AgentOn(types={"baja:MyNiagaraType", "baja:AnotherType"}, requiredPermissions="w", app="someApp", defaultAgent=Preference.PREFERRED),
    @AgentOn(types="baja:YetAnotherType"),
  },
  adapter=@Adapter(from="fromInstance", to="toInstance"),
  ext={@FileExt(name="myFileExt")},
  ordScheme="myOrdScheme"
)

The @NiagaraType annotation is the most important annotation and is the only annotation that all Niagara types must have, even those that do not declare any slots. A @NiagaraType annotation is necessary for the automatic update of module-include.xml and moduleTest-include.xml.

Unlike the other Niagara annotations, @NiagaraType is processed at compile time by an annotation processor and not by slot-o-matic. This compile-time processing is responsible for updating the module-include.xml or moduleTest-include.xml files with the correct Baja type information used by the system registry.

@NiagaraType is a normal annotation, but all of its attributes are optional. If none of the attributes are needed,@NiagaraType can be treated as a marker annotation.

The @NiagaraType annotation has several attributes, detailed below.

agent attribute

The agent attribute defines which class, if any, the BObject annotated with @NiagaraType is an agent on. This attribute is an array of @AgentOn annotations, and is optional.

@AgentOn
(
 types="module:TypeName",    //The target types that the agent is registered to.  This is a String array
 requiredPermissions="w",    //The required permissions could be "r", "w", "rw", or not used at all.
 app="myAppName",            //influence on the registry to set the correct default agent and remove the agents that do not mix well their their application.
 defaultAgent=Preference.PREFERRED   //Chooses where the agent is the preferred default.  Options are: Preference.PREFERRED, Preference.NORMAL, Preference.NOT_PREFERRED
 )

Since Annotations cannot contain actual BObjects as attributes, the @AgentOn annotation wraps information about what Baja types a class is an agent on.

A @NiagaraType annotation may have any number of @AgentOn annotations. Additionally, @AgentOn annotations can declare a type as an agent on multiple types.

An @AgentOn annotation has four attributes:

adapter attribute

The adapter attribute specifies a class as an “Adapter” from a class to a different class. An adapter converts instances of one type to instances of another type. This attribute is a single @Adapter annotation, and is optional.

@Adapter
(
  from="nameOfFromType",
  to="nameOfToType"
)

An @Adapter annotation defines the source type and target type of an adapter class. It has two attributes:

ext attribute

If a given class models a file type, this attribute lists the extensions of the file types it models. This attribute is an array of @FileExt annotations, and is optional

The @FileExt annotation

@FileExt
(
  name="extName"   //The name of the file extension
)

A @FileExt annotation defines a single file extension. It has a single attribute, “name”, which is a String representing the file extension.

ordScheme attribute

The ordScheme attribute is a single String which defines the ord scheme used by the class. It is optional.

Slot annotations

Slot annotations are annotations which define the frozen slots of a Baja class. These annotations are processed by the Slot-o-matic tool to automatically generate the necessary fields and methods for a Baja class.There are three slot annotations, one for each slot type. A class may have any number of these slot annotations.

Non-class annotations

As mentioned, annotations cannot contain attributes that are Java Objects. As such, annotations were created which serve as data providers for the slot annotations where primitives and String types are not sufficient. These annotations cannot be applied to a class, but are used as attributes for the class annotations listed below.

@Facet

@Facet(name = "BFacets.MIN", value = "3")
@Facet("BUtilityClass.getFacetList()")

A @Facet annotation describes a single slot facet, either as a key/value pair or as a simple value. It has two attributes:

Common attributes

In addition to slot-specific attributes, all of the slot annotations have the following attributes:

@NiagaraProperty

/**
 *  This is my property.  This comment will be included in the slot definition. Please note that this is a javaDoc comment.
 */
@NiagaraProperty
(
  name = "myFirstProperty",                //REQUIRED Name of the property
  type = "baja:RelTime",                   //REQUIRED Property type. Supports "Module:Type" typespec format (preferred) or full name.Example: "baja:RelTime" or "BRelTime" are valid.
  defaultValue = "BRelTime.makeHours(1)",  //REQUIRED The default value of the property
  flags= Flags.HIDDEN | Flags.ASYNC,       //Any flags the property my have. Notice that multiple flags are grouped together with the "|" symbol.
  facets =                                 //Any facets that the property may have.  The facet annotation works as a name-value pair. 
  {
    @Facet(name = "BFacets.MIN", value = "BRelTime.makeSeconds(0)"),
    @Facet(name = "BFacets.MAX", value = "BRelTime.makeSeconds(60)")
  }
)

A @NiagaraProperty annotation defines a single Property on a BComplex. A BComplex can have any number of properties, each declared in a separate @NiagaraProperty annotation.

A @NiagaraProperty annotation has the following attributes, in addition to the standard attributes detailed above:

@NiagaraAction

/**
 *  This is my action.  This comment will be included in the slot definition. Please note that this is a javaDoc comment.
 */
@NiagaraAction
(
  name = "myOnlyAction",                        //REQUIRED Name of the action.
  flags = Flags.HIDDEN,                         //Any flags the action my have. Note that multiple flags are grouped together with the "|" symbol.
  parameterType = "baja:RelTime",               //Parameter type of the action. Supports "Module:Type" typespec format (preferred) or full name. Example: "baja:RelTime" or "BRelTime" are valid.
  defaultValue = "BRelTime.makeSeconds(5)",     //The default value of the action.
  returnType = "baja:RelTime",     //Return type of the action. Supports "Module:Type" typespec format (preferred) or full name. Example: "baja:RelTime" or "BRelTime" are valid.
  facets =                                      //Any facets that the action may have.  The facet annotation works as a name-value pair.
  {
    @Facet(name = "BFacets.MIN", value = "BRelTime.makeSeconds(0)"),
    @Facet(name = "BFacets.MAX", value = "BRelTime.makeSeconds(60)")
  }
)

A @NiagaraAction annotation defines a single Action on a BComplex. A BComplex can have any number of actions, each declared in a separate @NiagaraAction annotation.

A @NiagaraAction annotation has the following attributes, in addition to the standard attributes detailed above:

@NiagaraTopic

/**
 *  This is my topic.  This comment will be included in the slot definition. Please note that this is a javaDoc comment.
 */
@NiagaraTopic
(
  name = "myFirstTopic",                //REQUIRED Name of the topic.
  flags = Flags.USER_DEFINED_1,         //Any flags the topic my have. Note that multiple flags are grouped together with the "|" symbol.
  eventType = "int",        //Event type of the topic. Supports "Module:Type" typespec format (preferred) or full name. Example: "baja:RelTime" or "BRelTime" are valid.
  facets =                              //Any facets that the topic may have.  The facet annotation works as a name-value pair.
  {
    @Facet(name = "BFacets.MIN", value = "1"),
    @Facet(name = "BFacets.MAX", value = "40")
  }
)

A @NiagaraTopic annotation defines a single Topic of a BComplex. A BComplex can have any number of topics, each declared in a separate @NiagaraTopic annotation.

A @NiagaraTopic annotation has the following attribute, in addition to the standard attributes detailed above:

@NiagaraEnum

@NiagaraEnum
(
  range = {
    @Range("zero"),                   // Let Slot-o-matic number these
    @Range("one"),
    @Range(value="two"),              // Can explicitly use value as the key, if needed
    @Range(value="three", ordinal=3), // Must explicitly use all keys if specifying ordinal
    @Range(value="four", ordinal=5)
  },
  defaultValue = "one"                // If this was not present, the default would be "zero"
)

A @NiagaraEnum annotation specifies that a given class is an enum. Classes annotated with @NiagaraEnum should extend from BFrozenEnum, but this is not enforced by Slot-o-matic. A class may only have one @NiagaraEnum annotation, and it may not have both a @NiagaraEnum annotation and any other Niagara annotations besides @NiagaraType

A @NiagaraEnum annotation has two attributes:

@Range

A @Range annotation is used by the @NiagaraEnum to build up its range list. It has two attributes:

A range annotation can be specified as just @Range("name") if auto-numbering is necessary, or can be specified with an ordinal as @Range(value="name", ordinal=5). However,@Range("name", ordinal=5) is not valid syntax.

@NiagaraSingleton

@NiagaraSingleton

A @NiagaraSingleton annotation specifies that a given class is a singleton class. The presence of this annotation will cause Slot-o-matic to emit a public static final INSTANCE field in addition to the TYPE field.

@NiagaraSingleton is a marker annotation with no attributes

Examples

A simple class with slots

/**
 * Test input for multiple annotation slot code
 */
@NiagaraType
/** foo is the property used for stuff */
@NiagaraProperty(
  name = "foo",
  type = "String",
  defaultValue = "sfafasf"
)
/** bar is another property used for stuff.
 *  It's more complicated, so the javadoc takes more lines
 *  or something like that
 */
@NiagaraProperty(
  name = "bar",
  type = "BString",
  defaultValue = "BString.make(\"bar\")",
  flags = Flags.HIDDEN | Flags.TRANSIENT
)
/** This action will cause things to happen. Probably. */
@NiagaraAction(
  name = "things",
  returnType = "int"
)
/** This event is fired when bar changes */
@NiagaraTopic(
  name = "barChanged",
  eventType = "BString",
  flags = Flags.HIDDEN
)
public class BMultiAnnotationFoobar extends BComponent
{
}

An enum class

/**
 * Simple Enum class
 */
@NiagaraType
@NiagaraEnum(
  range = {
    @Range("deny"),
    @Range("sameorigin"),
    @Range("any"),
    @Range(value="none", ordinal=5)
  },
  defaultValue = "sameorigin"
)
public class BEnumTest extends BFrozenEnum
{
}

A singleton class

/**
 * Singleton class
 */
@NiagaraType
@NiagaraSingleton
public class BSingletonTest extends BSingleton
{
}

Overriding a slot

Note the generated code included in this example does not have any get, set, fire, or invoke methods generated for these slots

/**
 * Test slot override mechanism
 */
@NiagaraType
@NiagaraProperty(
  name = "overrideProp",
  type = "String",
  defaultValue = "Foo",
  flags = Flags.HIDDEN,
  override = true
)
@NiagaraTopic(
  name = "overrideTopic",
  override = true
)
@NiagaraAction(
  name = "overrideAction",
  flags = Flags.HIDDEN,
  override = true
)
public class BTestSlotOverride
  extends BMyClass
{
/*+ ------------ BEGIN BAJA AUTO GENERATED CODE ------------ +*/
/*@ $slotomaticModule.annotationTests.BTestSlotOverride(1213737594)1.0$ @*/
/* Generated Fri Sep 11 09:16:22 EDT 2015 by Slot-o-Matic (c) Tridium, Inc. 2012 */
////////////////////////////////////////////////////////////////
// Property "overrideProp"
////////////////////////////////////////////////////////////////
   
  /**
   * Slot for the {@code overrideProp} property.
   * @see #getOverrideProp
   * @see #setOverrideProp
   */
  public static final Property overrideProp = newProperty(Flags.HIDDEN, "Foo",null);
////////////////////////////////////////////////////////////////
// Action "overrideAction"
////////////////////////////////////////////////////////////////
   
  /**
   * Slot for the {@code overrideAction} action.
   * @see #overrideAction()
   */
  public static final Action overrideAction = newAction(Flags.HIDDEN,null);
////////////////////////////////////////////////////////////////
// Topic "overrideTopic"
////////////////////////////////////////////////////////////////
   
  /**
   * Slot for the {@code overrideTopic} topic.
   * @see #fireOverrideTopic
   */
  public static final Topic overrideTopic = newTopic(0,null);
////////////////////////////////////////////////////////////////
// Type
////////////////////////////////////////////////////////////////
   
  @Override
  public Type getType() { return TYPE; }
  public static final Type TYPE = Sys.loadType(BTestSlotOverride.class);
/*+ ------------ END BAJA AUTO GENERATED CODE -------------- +*/
}

Migrating Niagara AX slot-o-matic code

In Niagara AX, a different format for slot-o-matic code was used. This format used a special Java comment block with additional characters to indicate the presence of a Baja comment block. While this syntax will continue to be supported in N4, it is highly recommended that developers migrate to using the new Annotation syntax. Baja comment block syntax will not receive any new features (including slot overrides).

To make the task of migrating old slot-o-matic code easier, the slot-o-matic tool can automatically migrate existing Baja comment block code to Annotation code.

Caveats

While the migration process is robust, there are still several caveats to keep in mind when running the slot-o-matic migration tool:

Running the migration

The migration is a two-step process:

  1. Parse Java files containing Baja comment block and replace the Baja comment block with Annotation code
  2. Re-slot the Java file using the annotation code data

The recommended approach is to perform a two-pass compile and do both steps at once, using the following command:

gradlew :module-rt:slotomatic -Dslotomatic.migrateBeforeRecompile

However, the migration task can be invoked without running a slot-o-matic compile:

gradlew :module-rt:migrateSlotomatc