Reference paths.
A reference path is a
String specifying a path from some implicit starting object type, through zero
or more reference fields, ending up at some target object type(s).
In other words, given some starting object(s), a reference path takes you through a path of references to the target object(s).
Note that the number of target objects can be vastly different than the number of starting objects, depending on
the fan-in/fan-out of the references traversed.
Specifying Fields
The starting object type is always implicit from the context in which the path is used. The
String path describes
the reference fields, i.e., the "steps" in the path that travel from the starting object type to the target object type.
The steps are separated by dot (
".") characters.
The reference fields in the path may be traversed in either the forward or inverse direction:
to traverse a field in the forward direction, specify name of the field; to traverse a field in the inverse direction,
specify the name of the field and its containing type using the syntax
^Type:field^.
For complex fields, specify both the field and sub-field: for
Set and
java.util.List fields, the sub-field is
always
"element", while for
java.util.Map fields the sub-field is either
"key" or
"value".
For example, to traverse a map field's
key sub-field, specify
"mymap.key".
Target Fields
A reference path may optionally have a target field appended to the end of the path. The target field does not have
to be a reference field. If a target field is specified, the target object types
are restricted to those types containing the target field.
Note that to avoid ambiguity it must be known at the time the path is parsed whether the path contains a target field: for
example, consider the path
"parent.parent" in the context of a
Child object: if there is a target field,
the target object is the child's parent and the target field is the
"parent" field of the child's parent
(which just happens to also be a reference field), but if there is no target field, the path simply refers to the child's
grandparent.
Examples
Considering the following model classes:
@PermazenType
public class Employee {
public abstract String getName();
public abstract void setName(String name);
public abstract Employee getManager();
public abstract void setManager(Employee manager);
public abstract Set<Asset> getAssets();
}
@PermazenType
public class Asset {
public abstract int getAssetId();
public abstract void setAssetId(int assetId);
public abstract String getName();
public abstract void setName(String name);
}
Then these paths have the following meanings:
Start Type |
Path |
Has Target Field? |
Description |
Employee |
"" |
No |
The
Employee |
Employee |
"" |
Yes |
Invalid - no target field specified |
Employee |
"name" |
Yes |
The
Employee's name |
Employee |
"name" |
No |
Invalid -
"name" is not a reference field |
Asset |
"name" |
Yes |
The
Asset's name |
Object |
"name" |
Yes |
The
Employee's or
Asset's name |
Employee |
"manager" |
No |
The
Employee's manager |
Employee |
"manager" |
Yes |
The
"manager" field of the
Employee |
Employee |
"manager.name" |
Yes |
The
Employee's manager's name |
Employee |
"^Employee:manager^.name" |
Yes |
The names of all of the
Employee's direct reports |
Employee |
"manager.assets.description" |
Yes |
The descriptions of the
Employee's manager's assets |
Employee |
"manager.^Employee:manager^" |
No |
All of the
Employee's manager's direct reports |
Asset |
"^Employee:assets.element^" |
No |
The employee owning the
Asset |
Asset |
"^Employee:assets.element^" |
Yes |
Invalid - no target field specified |
Asset |
"^Employee:assets.element^
.manager.^Employee:manager^.asset.assetId" |
Yes |
ID's of all
Assets owned by direct reports of the manager of the
Employee owning the original
Asset |
Fields of Sub-Types
The same field can appear in multiple types, e.g.,
"name" in the example above appears in both
Employeeand
Asset. The set of all possible object types is recalculated at each step in the reference path, including
at the last step, which gives the target object type(s). At each intermediate step, as long as the Java types do not
contain incompatible definitions for the named field, the step is valid.
In rare cases where multiple sub-types of a common super-type type have fields with the same name but different storage IDs,
the storage ID may be explicitly specified as a suffix, for example,
"name#123".
Using Reference Paths
Reference paths may be explicitly created via
Permazen#parseReferencePathand traversed in the forward direction via
JTransaction#followReferencePathor in the inverse direction via
JTransaction#invertReferencePath.
Reference paths are also used implicitly by
io.permazen.annotation.OnChange annotations to
specify non-local objects for change monitoring, and by
io.permazen.annotation.FollowPathannotations.