Class ReferencePath
Overview
A reference path defines a bi-directional path of object references, starting from some starting object type(s) and ending up at some target object type(s) by hopping from object to object. Because reference fields are always indexed, given a set of starting or target instances Permazen can efficiently compute the set of objects at the other end of the path. This calculation includes automatic elimination of duplicates caused by loops or multiple paths.
The reference fields in the path may be simple fields or sub-fields of complex fields (i.e., list element, set element,
map key, or map value), and each field may be traversed in either the forward or inverse direction. In short, a
ReferencePath
consists of a set of starting object types, a list of reference fields, and a boolean flag
for each field that determines the direction the field should be traversed.
When stepping forward through a complex field, or backward through any field, the number of reachable objects can increase.
In general, the number of target objects can be vastly different than the number of starting objects, depending on the
fan-in/fan-out of the reference fields traversed. This should be kept in mind when considering the use of reference paths.
A ReferencePath
containing only forward simple reference fields is termed singular.
Type Pruning
At each step in the path, there is a set of possible current object types: at the initial step, this set is just the starting object types, and after the final step, this set becomes the target object types. At each step, some of the current object types may get pruned because they are incompatible with the next field in the path. This happens when the field is only defined in some of the types (for forward steps), or when the field can only refer to some of the types (for inverse steps). In these cases, the search ends for any pruned types and continues for the remaining types. It is an error if, at any step, all types are pruned, as this would imply that no objects could ever be found.
String Form
Reference paths are denoted in String
form as a concatenation of zero or more reference steps:
- Forward reference steps are denoted by either
"->fieldName"
or (rarely)"->TypeName.fieldName"
. The latter form is only needed to disambiguate when two or more of the current object types define incompatible fields with the same name. - Inverse reference steps are denoted
"<-TypeName.fieldName"
wherefieldName
is the name of a reference field defined inTypeName
(or some sub-type(s) therein).
To parse a reference path into a ReferencePath
instance, the path must be interpreted in the context of
some starting object types (see Permazen.parseReferencePath()
).
Type Names
The TypeName
is either the name of an object type in the schema (usually the unqualified name of the corresponding
Java model class), or else the fully-qualified name of any Java class or interface.
Field Names
For simple reference fields, the fieldName
is just the field name. For reference fields that are sub-fields
of complex fields, fieldName
must specify both the parent field and the sub-field:
- For
Map
fields, specify the sub-field via eithermyfield.key
ormyfield.value
. - For
List
andSet
fields, the only sub-field iselement
, so you can specifymyfield.element
or abbreviate asmyfield
.
Examples
Consider the following model classes:
@PermazenType
public interface Animal<T extends Animal<T>> {
T getParent();
void setParent(T parent);
Set<Animal<?>> getEnemies();
}
@PermazenType
public interface Elephant extends Animal<Elephant> {
Elephant getFriend();
void setFriend(Elephant friend);
}
@PermazenType
public interface Giraffe extends Animal<Giraffe> {
Giraffe getFriend();
void setFriend(Giraffe friend);
}
Then the reference paths below would have the following meanings:
Start Type | Path | Target Types | Description |
---|---|---|---|
Elephant |
"" |
Elephant |
The starting Elephant |
Elephant |
"->parent" |
Elephant |
The Elephant 's parent |
Giraffe |
"->parent" |
Giraffe |
The Giraffe 's parent |
Animal |
"->parent" |
Elephant , Giraffe |
The Animals 's parent |
Elephant |
"<-Elephant.enemies" |
Elephant |
All Elephant s for whom the original Elephant is an enemy |
Elephant |
"<-Animal.enemies" |
Elephant , Giraffe |
All Animal s for whom the original Elephant is an enemy |
Elephant |
"<-friend" |
Elephant |
All Elephant s for whom the original Elephant is their friend |
Animal |
"<-friend" |
Elephant , Giraffe |
All Animal s for whom the original Animal is their friend |
Elephant |
"->friend<-Giraffe.enemies" |
Giraffe |
All Giraffe s for whom the original Elephant 's friend is an enemy |
Elephant |
"->enemies<-Giraffe.friend ->enemies<-Elephant.friend" |
Elephant |
All Elephant s who's friend is an enemy of some Giraffe for whom one of the original
Elephant 's enemies is their friend |
Elephant |
"<-Giraffe.friend" |
N/A | Invalid - it's not possible for an Elephant to be a Giraffe 's friend |
Using Reference Paths
Reference paths may be explicitly created via Permazen.parseReferencePath()
and traversed in the forward direction via
PermazenTransaction.followReferencePath()
or in the inverse direction via PermazenTransaction.invertReferencePath()
.
The @ReferencePath
annotation can be used to auto-generate methods
that traverse reference paths.
Reference paths are also used by @OnChange
and @OnDelete
annotations to specify
non-local objects for monitoring.
-
Method Summary
Modifier and TypeMethodDescriptionboolean
List<Set<PermazenClass<?>>>
Get the current object types at each step in the path.int[]
Get the storage IDs of the reference fields in this path in the order they occur.Class<?>
Get the narrowest possible Java type of the object(s) at which this path starts.Set<PermazenClass<?>>
Get the possible model object types for the objects at the start of this path.Class<?>
Get the narrowest possible Java type of the object(s) at which this path ends.Set<PermazenClass<?>>
Get the possible model object types for the objects at the end of this path.int
hashCode()
boolean
isEmpty()
Determine whether this path is empty, i.e., contains zero steps.boolean
Determine whether traversing this path can result in only one object being found.int
size()
Get the number of steps in this reference path.toString()
Get theString
form of the path associated with this instance.
-
Method Details
-
getStartingTypes
Get the possible model object types for the objects at the start of this path.This method returns the first set in the list returned by
getCurrentTypesList()
. If this path has zero length, then this method returns the same set asgetTargetTypes()
.The returned set will contain a null element when the target object can possibly be an
UntypedPermazenObject
.- Returns:
- non-empty set of model object types at which this reference path starts, possibly including null
-
getStartingType
Get the narrowest possible Java type of the object(s) at which this path starts.The returned type will be as narrow as possible while still including all possibilities, but note that it's possible for there to be multiple candidates for the "starting type", none of which is a sub-type of any other. To retrieve all such starting types, use
getStartingTypes()
; this method just invokesTypeTokens.findLowestCommonAncestorOfClasses()
on that result.- Returns:
- the Java type at which this reference path starts
-
getTargetTypes
Get the possible model object types for the objects at the end of this path.This method returns the last set in the list returned by
getCurrentTypesList()
. If this path has zero length, then this method returns the same set asgetStartingTypes()
.The returned set will contain a null element when the target object can possibly be an
UntypedPermazenObject
.- Returns:
- non-empty set of model object types at which this reference path ends, possibly including null
-
getTargetType
Get the narrowest possible Java type of the object(s) at which this path ends.The returned type will be as narrow as possible while still including all possibilities, but note that it's possible for there to be multiple candidates for the "target type", none of which is a sub-type of any other. To retrieve all such target types, use
getTargetTypes()
; this method just invokesTypeTokens.findLowestCommonAncestorOfClasses()
on that result.- Returns:
- the Java type at which this reference path ends
-
getCurrentTypesList
Get the current object types at each step in the path.The returned list always has length one more than the length of the array returned by
getReferenceFields()
, such that the set at index i contains all possible types found after the ith step. The first element contains the starting object types and the last element contains the target types.A set in the list will contain a null element if an object at that step can possibly be an
UntypedPermazenObject
.- Returns:
- list of the possible
PermazenClass
's appearing at each step in this path, each of which is non-empty and may include null
-
getReferenceFields
public int[] getReferenceFields()Get the storage IDs of the reference fields in this path in the order they occur.Storage ID's will be negated to indicate reference fields traversed in the inverse direction.
The result will be empty if this path is empty.
- Returns:
- zero or more possibly negated reference field storage IDs
-
isSingular
public boolean isSingular()Determine whether traversing this path can result in only one object being found.An empty path is always singular - it always returns just the starting object.
- Returns:
- true if this path only includes forward simple field references, otherwise false
-
size
public int size()Get the number of steps in this reference path.- Returns:
- the length of this path
-
isEmpty
public boolean isEmpty()Determine whether this path is empty, i.e., contains zero steps.- Returns:
- true if this path is empty
-
toString
Get theString
form of the path associated with this instance. -
equals
-
hashCode
public int hashCode()
-