Core Concepts

GlobsFramework revolves around four interlocking abstractions: GlobType, Field, Glob, and Annotations. Understanding them unlocks the whole framework.

GlobType — the schema

A GlobType is the runtime schema for a family of data objects. It plays the role that a Java Class plays for beans, but it is richer: it lists fields, their types, and any metadata annotations. Unlike a Class, it can be created dynamically at runtime without generating bytecode.

public interface GlobType {
    String          getName();
    Field[]         getFields();
    Field           getField(String name) throws ItemNotFound;
    <T extends Field> T getTypedField(String name);
    MutableGlob     instantiate();
    Glob            getAnnotation(Key key);
}

Static definition (compile-time)

Declare fields as public static members. The loader fills them in at class initialization time using the field names as the schema names:

public class OrderType {
    public static final GlobType     TYPE;
    public static final LongField    orderId;
    public static final StringField  customerName;
    public static final DoubleField  totalAmount;
    public static final BooleanField shipped;

    static {
        GlobTypeBuilder b = GlobTypeBuilderFactory.create("Order");
        orderId      = b.declareLongField("orderId");
        customerName = b.declareStringField("customerName");
        totalAmount  = b.declareDoubleField("totalAmount");
        shipped      = b.declareBooleanField("shipped");
        TYPE         = b.build();
    }
}

Dynamic definition (runtime)

Build a GlobType from a JSON schema, a database result set, or any external source — no Java class needed:

GlobType orderType = GlobTypeBuilderFactory.create("Order")
    .addLongField("orderId")
    .addStringField("customerName", NamingField.UNIQUE_GLOB)
    .addDoubleField("totalAmount")
    .addBooleanField("shipped")
    .build();

Field — typed accessor

A Field is both a key into a Glob's data and a carrier of type information. Rather than accessing data by string name (map.get("price")), you use a typed Field object. The compiler knows the return type; no cast is needed.

Built-in field types

Scalar fields

IntegerField LongField DoubleField StringField BooleanField BlobField DateField DateTimeField

Array fields

IntegerArrayField LongArrayField DoubleArrayField StringArrayField

Nested Globs

GlobField — embed a single child Glob
GlobArrayField — embed a list of child Globs
GlobUnionField — embed a polymorphic Glob

The visitor pattern

Generic code — such as a serializer — uses the visitor pattern to dispatch on field type without instanceof chains or reflection:

for (Field field : globType.getFields()) {
    field.accept(new FieldVisitor.AbstractWithErrorVisitor() {
        public void visitString(StringField f) {
            String val = glob.get(f);
            // write string to output...
        }
        public void visitDouble(DoubleField f) {
            Double val = glob.get(f);
            // write number to output...
        }
        public void visitGlob(GlobField f) {
            Glob child = glob.get(f);
            // recurse...
        }
    });
}

This is exactly how the JSON, XML, and binary serializers in the ecosystem are built — and why you can add a new GlobType without touching them.

Glob — the data container

A Glob is an instance of a GlobType: it holds values for each field and always knows its own type. Unlike a raw Map<String,Object>, access is type-safe and the schema is always available.

Reading values

Glob g = /* from JSON / DB / HTTP ... */;

// Null-safe typed access
String  name   = g.get(OrderType.customerName);     // String or null
double  amount = g.get(OrderType.totalAmount, 0.0); // default if null
Optional<Double> opt = g.getOpt(OrderType.totalAmount);

// Know whether a field was set vs. not present
boolean hasValue = g.isSet(OrderType.shipped);
boolean isNull   = g.isNull(OrderType.shipped);

// Access the schema at any time
GlobType type = g.getType();

MutableGlob — building & updating

MutableGlob order = OrderType.TYPE.instantiate()
    .set(OrderType.orderId,      1001L)
    .set(OrderType.customerName, "Alice")
    .set(OrderType.totalAmount,  249.99);

// Update later
order.set(OrderType.shipped, true);

// Remove a value (goes back to "unset" state)
order.unset(OrderType.totalAmount);

Annotations — metadata as Globs

In GlobsFramework, annotations on fields and types are themselves Globs. This means they can carry structured data, be queried at runtime, and be composed — far beyond what Java's @Annotation can express.

Attaching an annotation

// NamingField is a built-in annotation that marks the "display name" field
// Use it when building a type dynamically:
GlobTypeBuilderFactory.create("Product")
    .addStringField("title", NamingField.UNIQUE_GLOB)
    .addDoubleField("price")
    .build();

// Or attach it in a static type using the @NamingField_ annotation:
public class ProductType {
    public static GlobType    TYPE;
    @NamingField_  // for documentation : not used
    public static StringField title;
    static {
        GlobTypeBuilder b = GlobTypeBuilderFactory.create("Product");
        title = b.declareStringField("title", NamingField.UNIQUE_GLOB);
        TYPE  = b.build();
    }
}

Querying annotations at runtime

// Find which field is "the naming field" — without hardcoding the field name
Field namingField = glob.getType()
    .findFieldWithAnnotation(NamingField.KEY);

String label = glob.getValue(namingField);// "XPhone", "Order #1001", ...

Annotations are the mechanism behind many framework features: marking the primary key for database tables, specifying HTTP parameter binding, defining display labels in the UI layer, and more.

Set vs. null — a subtle but important distinction

A Glob tracks two independent states for each field:

StateisSet(f)isNull(f)Typical use
Never assigned false true Field not present in source (e.g. missing JSON key)
Explicitly set to null true true Source had a null / SQL NULL value
Set to a value true false Normal non-null value

This distinction is crucial in PATCH semantics (only serialize fields that were actually set) and in sparse data models (distinguish "field absent" from "field is null").