Package io.permazen

Class ReferencePath

java.lang.Object
io.permazen.ReferencePath

public class ReferencePath extends Object
Reference paths.

Overview

A reference path defines a path of object references, starting from an instance of some starting object type and ending up at one or more target objects (having some target object types), by hopping from object to object through a sequence of reference fields. The reference fields may be simple reference fields or sub-fields of complex fields (i.e., list or set element, or map key or value), and may be traversed in either direction (forward or inverse). At its heart, a reference path is simply a list of reference fields along with a flag for each field that determines in which direction the reference should be traversed.

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 type, and after the final step, this set equals the target object types. It is possible for any step to only apply to some of the current object types. This can happen when the field is only defined in some of the object types (for forward step) or when the field can only refer to some of them (for inverse steps). In these cases, the search ends for any unmatched types and continues for matching types. However, it is an error if, at any step, no types match, which would mean no objects could ever be found. It's also an error if the field specified at any step is ambiguous, which can happen when two sub-types define incompatible fields using the same name.

When stepping through a collection field in the forward direction, or through any field in the inverse direction, the number of objects can multiply. 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 references traversed.

Reference Steps

Reference paths are denoted in String form as zero or more concatenated reference steps:

  • Forward reference steps are denoted "->fieldName" where fieldName is the name of a reference field defined in at least one of the current object types.
  • Inverse reference steps are denoted "<-TypeName.fieldName" where fieldName is the name of a reference field defined in TypeName (or some sub-type).

Field Names

In the case of complex fields, the fieldName must specify the sub-field:

  • For List and Set fields, the only sub-field is element; for these field types, myfield can be used as an abbreviation for myfield.element.
  • For Map fields, either myfield.key or myfield.value must be specified.

In rare cases, sub-types of a common super-type type have fields with the same name but different storage IDs. To disambiguate, the storage ID may always be explicitly specified as a suffix like this: "myfield#123".

Type Names

For inverse steps, the TypeName must be given to specify the type of the referring object. TypeName is either the name of an object type in the schema (typically this is the unqualified class name of the corresponding Java model class), or any fully-qualified Java class name.

Examples

Consider the following model classes:

 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 these paths have the following meanings:
Reference Path Examples
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" ElephantGiraffe The Animals's parent
Elephant "<-Elephant.enemies" Elephant All Elephants for whom the original Elephant is an enemy
Elephant "<-Animal.enemies" ElephantGiraffe All Animals for whom the original Elephant is an enemy
Elephant "<-friend" Elephant All Elephants for whom the original Elephant is their friend
Animal "<-friend" ElephantGiraffe All Animals for whom the original Animal is their friend
Elephant "->friend<-Giraffe.enemies" Giraffe All Giraffes for whom the original Elephant's friend is an enemy
Elephant "->enemies<-Giraffe.friend
->enemies<-Elephant.friend"
Elephant All Elephants 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().

Reference paths are also used by @OnChange annotations to specify non-local objects for change monitoring, and by @ReferencePath annotations.

See Also:
  • Method Details

    • getStartingTypes

      public Set<PermazenClass<?>> 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 as getTargetTypes().

      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

      public Class<?> 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 invokes Util.findLowestCommonAncestorOfClasses() on that result.

      Returns:
      the Java type at which this reference path starts
    • getTargetTypes

      public Set<PermazenClass<?>> 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 as getStartingTypes().

      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

      public Class<?> 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 invokes Util.findLowestCommonAncestorOfClasses() on that result.

      Returns:
      the Java type at which this reference path ends
    • getCurrentTypesList

      public List<Set<PermazenClass<?>>> 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 reverse 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

      public String toString()
      Get the String form of the path associated with this instance.
      Overrides:
      toString in class Object
    • equals

      public boolean equals(Object obj)
      Overrides:
      equals in class Object
    • hashCode

      public int hashCode()
      Overrides:
      hashCode in class Object