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:
| State | isSet(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").