Package io.permazen

Class ReferencePath

java.lang.Object
io.permazen.ReferencePath

public class ReferencePath extends Object
Permazen reference paths.

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" where fieldName is the name of a reference field defined in TypeName (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 either myfield.key or myfield.value.
  • For List and Set fields, the only sub-field is element, so you can specify myfield.element or abbreviate as myfield.

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

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.

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 TypeTokens.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 TypeTokens.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 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

      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