Basics

Guides

API Reference

Menu

Basics

Guides

API Reference

Annotations Guide

Annotations are metadata markers that can be attached to classes, members, and methods. They do not affect execution by default but can be read at runtime using the reflect module. This makes them useful for building frameworks, documentation generators, validation engines, and other tools that need to inspect code structure.


Annotation Syntax

An annotation starts with @ followed by the annotation name. Annotations may optionally include named key-value arguments inside parentheses. All argument values are strings.

Without arguments:

@Before
public setUp() { }

With one argument:

@Test(name = "My test name")
public myTest() { }

With multiple arguments:

@Route(method = "GET", path = "/api/users")
public getUsers() { }

Multiple annotations can be stacked on the same declaration.


Where Annotations Can Be Applied

Annotations can be placed on three kinds of declarations.

Classes

@Test(name = "Calculator Tests")
class calculatorTests {
    // ...
}

Members

class product {
    @Required
    public name = "";

    @Min(value = "0")
    @Max(value = "9999")
    public price = 0.0;

    @Mock
    public service;
}

Methods

class orderController {
    @Route(method = "GET", path = "/orders")
    public listOrders() { }

    @Route(method = "POST", path = "/orders")
    @Auth(role = "admin")
    public createOrder() { }
}

Accessing Annotations via Reflection

To read annotations at runtime you need the reflect module.

include reflect;

The main entry point is reflect.getClassDef(ClassName), which returns an RClass object for the named class. From there you can call getMembers() and getMethods() to get full definitions including any annotations.

Note: Some reflection functions require security manager permissions. The evalStr, evalFile, and includeModule functions are gated by the reflect.eval.string, reflect.eval.file, and reflect.include.module properties respectively, and are disabled by default. getClassDef and related introspection functions may also be restricted depending on how the security manager is configured in your environment. Check with your Aussom host application if reflection calls are not working as expected.


The Annotation Data Structure

Each annotation is represented as a map with two keys:

Key Type Description
annotationName string The name of the annotation without the @ prefix.
annotationArgs map A map of key-value pairs for the annotation's arguments. Keys and values are both strings. If the annotation has no arguments this map is empty.

Example for @Route(method = "GET", path = "/orders"):

{
    annotationName: "Route",
    annotationArgs: {
        method: "GET",
        path: "/orders"
    }
}

Example for @Before (no arguments):

{
    annotationName: "Before",
    annotationArgs: {}
}

The annotations field on a member or method definition is a list of these annotation maps, so a declaration with multiple annotations will have multiple entries in the list.


Reading Member Annotations

RClass.getMembers() returns a map where each key is a member name and the value is a definition map with the following fields:

Key Type Description
type string The declared type of the member (e.g. STRING, INT, NULL, UNDEF).
annotations list A list of annotation maps for this member.

Example:

include reflect;

class product {
    @Required
    public name = "";

    @Min(value = "0")
    @Max(value = "9999")
    public price = 0.0;

    public description = "";
}

class annotationExample {
    public main(args) {
        cls = reflect.getClassDef("product");
        members = cls.getMembers();

        for (memberName : members) {
            memberDef = members[memberName];
            annotations = memberDef["annotations"];

            if (#annotations > 0) {
                c.info("Member: " + memberName);
                for (ann : annotations) {
                    c.info("  @" + ann["annotationName"]);
                    annArgs = ann["annotationArgs"];
                    for (argKey : annArgs) {
                        c.info("    " + argKey + " = " + annArgs[argKey]);
                    }
                }
            }
        }
    }
}

Running this would output something like:

Member: name
  @Required
Member: price
  @Min
    value = 0
  @Max
    value = 9999

Reading Method Annotations

RClass.getMethods() returns a map where each key is a method name and the value is a definition map with the following fields:

Key Type Description
isExtern bool Whether the method is an extern method.
annotations list A list of annotation maps for this method.
arguments list A list of argument definition maps (see below).

Each argument definition map contains:

Key Type Description
name string The argument name, or "..." for variadic arguments.
requiredType string The declared type constraint (e.g. "string", "int"), or empty string if untyped.
hasDefaultValue bool Whether a default value is defined.
defaultValueType string The type of the default value (present when hasDefaultValue is true).
defaultValue string The string representation of the default value (present when hasDefaultValue is true).

Example:

include reflect;

class orderController {
    @Route(method = "GET", path = "/orders")
    public listOrders() { }

    @Route(method = "POST", path = "/orders")
    @Auth(role = "admin")
    public createOrder(string payload) { }
}

class annotationExample {
    public main(args) {
        cls = reflect.getClassDef("orderController");
        methods = cls.getMethods();

        for (methodName : methods) {
            methodDef = methods[methodName];
            annotations = methodDef["annotations"];

            if (#annotations > 0) {
                c.info("Method: " + methodName);
                for (ann : annotations) {
                    c.info("  @" + ann["annotationName"]);
                    annArgs = ann["annotationArgs"];
                    for (argKey : annArgs) {
                        c.info("    " + argKey + " = " + annArgs[argKey]);
                    }
                }
            }
        }
    }
}

Output:

Method: listOrders
  @Route
    method = GET
    path = /orders
Method: createOrder
  @Route
    method = POST
    path = /orders
  @Auth
    role = admin

Filtering by Annotation Name

A common pattern is to collect all methods or members that carry a specific annotation.

include reflect;

class myRouter {
    @Route(method = "GET", path = "/users")
    public getUsers() { }

    @Route(method = "GET", path = "/users/profile")
    public getProfile() { }

    public internalHelper() { }
}

class annotationExample {
    public main(args) {
        cls = reflect.getClassDef("myRouter");
        methods = cls.getMethods();
        routes = [];

        for (methodName : methods) {
            methodDef = methods[methodName];
            for (ann : methodDef["annotations"]) {
                if (ann["annotationName"] == "Route") {
                    routes @= {
                        method: methodName,
                        httpMethod: ann["annotationArgs"]["method"],
                        path: ann["annotationArgs"]["path"]
                    };
                }
            }
        }

        c.info("Discovered routes:");
        for (route : routes) {
            c.info("  " + route["httpMethod"] + " " + route["path"] + " -> " + route["method"] + "()");
        }
    }
}

Output:

Discovered routes:
  GET /users -> getUsers()
  GET /users/profile -> getProfile()

Complete Example

The following brings all the concepts together: annotated classes, members, and methods, reading them back via reflection, and checking for specific annotations.

include reflect;

// --- Annotated code ---

@Entity(table = "products")
class product {
    @Id
    public id = 0;

    @Column(name = "product_name")
    @Required
    public name = "";

    @Column(name = "unit_price")
    @Min(value = "0")
    public price = 0.0;

    @Transient
    public tempNote = "";
}

// --- Reflection consumer ---

class schemaInspector {
    public main(args) {
        cls = reflect.getClassDef("product");

        // Read class-level annotations
        c.info("Class: " + cls.getName());

        // Inspect all members
        members = cls.getMembers();
        c.info("Members:");
        for (memberName : members) {
            memberDef = members[memberName];
            annotations = memberDef["annotations"];
            annNames = [];
            for (ann : annotations) {
                annNames @= "@" + ann["annotationName"];
            }
            line = "  " + memberName + " [" + memberDef["type"] + "]";
            if (#annNames > 0) {
                line = line + " " + annNames.join(", ");
            }
            c.info(line);
        }

        // Find columns (members with @Column)
        c.info("Columns:");
        for (memberName : members) {
            for (ann : members[memberName]["annotations"]) {
                if (ann["annotationName"] == "Column") {
                    colName = ann["annotationArgs"]["name"];
                    c.info("  " + memberName + " -> column '" + colName + "'");
                }
            }
        }
    }
}

Output:

Class: product
Members:
  id [INT] @Id
  name [STRING] @Column, @Required
  price [DOUBLE] @Column, @Min
  tempNote [STRING] @Transient
Columns:
  name -> column 'product_name'
  price -> column 'unit_price'