Class Path

java.lang.Object
io.inversion.utils.Path

public class Path extends Object
A case insensitive utility abstraction for working with forward slash based paths /like/you/find/in/urls.

When working with Paths, leading and trailing slashes are completely disregarded. Any leading and trailing slashes in the documentation are illustrative only and could be excluded to achieve the same result. Multiple consecutive slashes are treated as a single slash. There will never be an empty string or null path part.

In addition to representing concrete paths, such as a file path or url path, a Path object may contain wildcards, regular expressions, and variable name bindings which are used when comparing a variablized abstract paths with a concrete path.

Paths are primarily used to configure Rules (Engine, Api, Endpoint and Action are all subclasses of Rule) to match against inbound Request urls to determine how the Request will be processed.

Paths can be variablized as follows:

  • /animals/* - will match anything that starts with "animals/". "*" matches any number of path segments including zero segments (meaning "animals/" alone will match and so will "animals/dogs/fido"). "*" wildcards are only valid as the last segment in a path.
  • /animals/dogs/:dogName - if a path segment starts with a ":" it indicates that the value can be anything but a value is required and should be mapped to the corresponding variable name, in this case "dogName", by whoever is doing the path matching.
  • /animals/dogs/{fido|ralph|jackie} - if the path segment is wrapped in "{}" the contents are considered a regular expression Pattern for matching
  • /animals/dogs/${fido|ralph|jackie} - a '$' can prefix '{}' as syntatic sugar some may be familiar with.
  • /animals/dogs/{dogName:fido|ralph|jackie} - again a regex, this time with a variable binding to "dogName"
  • /animals/[{dogs|cats|snakes}]/:animalName/* - if something is wrapped in square brackets "[]" that segment and all subsequent segments are optional. If the segments exists in the path being compared to, they must match the supplied rules, but if the comparison path ends right before the first optional segment, the paths still match.

When used in the context of a Api configuration you may see something like this:

  Engine e = new Engine().withIncludeOn(null, new Path("/apis"));
                         .withApi(new Api().withIncludeOn(null, new Path("bookstore/{storeName:johnsBestBooks|carolsBooksOnMain}"))
                                           .withEndpoint(new Endpoint().withIncludeOn(null, new Path("categories/:category/"))
                                                                       .withAction(new BrowseCategoriesAction().withIncludeOn(null, new Path("[:subcategory]/*")))));
 
See Also:
  • Constructor Summary

    Constructors
    Constructor
    Description
    Creates an empty Path
    Path(Path path)
    Creates a clone of the supplied Path
    Path(String... part)
    Constructs a Path based on all of the supplied parts.
    Path(List<String> parts)
    Convenience overload of Path(String...).
  • Method Summary

    Modifier and Type
    Method
    Description
    void
    add(String parts)
    Adds part to the end of the Path.
    boolean
     
    extract(Map params, Path toMatch)
    Convenience overloading of extract(Map, Path, boolean) with greedy = true.
    extract(Map params, Path matchingConcretePath, boolean greedy)
    Consumes the matching parts of matchingConcretePath and extracts any named variable to matchingConcretePath if this any of this paths parts contain variable bindings.
    Simple way to pull the first element of the path without having to check for size() > 0 first.
    get(int index)
    Simple way to get element at index without haveint to check for size() > index first.
    getVarName(int index)
    Extracts a variable name form the path expression if index exists and has a var name.
    boolean
    isOptional(int index)
    Square brackets, '[]', indicate that a path path (and by implication, all following parts) are considered optional for path matching.
    boolean
    isStatic(int index)
    Checks to see if the value at index is a wildcard, a variable, or is optional.
    boolean
    isVar(int index)
    Check to see if the value at index starts with '${', '{', ':' after removing any leading '[' characters.
    boolean
    isWildcard(int index)
    Check if the path part at index is equal to '*' without having to check if size() @lt; index first.
    Simple way to pull the last element of the path without having to check for size() > 0 first.
    boolean
    matches(Path concretePath)
    Checks if this Path is as case insensitive match, including any optional rules, wildcards, and regexes to concretePath.
    boolean
    matches(String toMatch)
    Convenience overloading of matches(Path).
    Gets the path parts as a List.
    remove(int index)
    Simple way to remove the path part at index without having to check for size() @lt; index first.
    int
     
    boolean
    startsWith(List<String> partsToMatch)
    Performs a case insensitive string match between this Path and pathsToMatch.
    subpath(int fromIndex, int toIndex)
    Creates a new sub Path.
     

    Methods inherited from class java.lang.Object

    clone, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
  • Constructor Details

    • Path

      public Path()
      Creates an empty Path
    • Path

      public Path(Path path)
      Creates a clone of the supplied Path
      Parameters:
      path - the Path to be cloned
    • Path

      public Path(String... part)
      Constructs a Path based on all of the supplied parts.

      The strings in part may themselves contain "/" characters and will be split into multiple parts correspondingly. Meaning Path p = new Path("part1", "part2/part3", "/part4/", "////part5//part6/", "part7") is valid and would result in a Path with parts "part1", "part2", "part3", "part4", "part5", "part6", "part7".

      Parameters:
      part - an array of path part strings
    • Path

      public Path(List<String> parts)
      Convenience overload of Path(String...).
      Parameters:
      parts - an list of path part strings
  • Method Details

    • parts

      public List<String> parts()
      Gets the path parts as a List.

      Method signature could easily have been "asList()"

      Returns:
      a new list with the individual path parts n the originally defined case.
    • first

      public String first()
      Simple way to pull the first element of the path without having to check for size() > 0 first.
      Returns:
      the first element in the path if it exists otherwise null
    • last

      public String last()
      Simple way to pull the last element of the path without having to check for size() > 0 first.
      Returns:
      the last element in the path if it exists otherwise null
    • get

      public String get(int index)
      Simple way to get element at index without haveint to check for size() > index first.
      Parameters:
      index - the index of the path part to retrive
      Returns:
      the path part at index if it exists otherwise null
    • add

      public void add(String parts)
      Adds part to the end of the Path.

      The parts is exploded via Utils.explode('/', part) first so while the part arg is a single value, it could result in multiple additions.

      Parameters:
      parts - path parts to add
    • remove

      public String remove(int index)
      Simple way to remove the path part at index without having to check for size() @lt; index first.
      Parameters:
      index - the index of the path part to remove
      Returns:
      the path part previously located at index if it existed otherwise null
    • startsWith

      public boolean startsWith(List<String> partsToMatch)
      Performs a case insensitive string match between this Path and pathsToMatch.

      Wildcards and regular expressions are not supported in this method, only straight case insensitive string comparison.

      Parameters:
      partsToMatch - the path parts to to match against
      Returns:
      true if each index of partsToMatch is a case insensitive match to this Path at the same index otherwise false.
    • size

      public int size()
      Returns:
      the number of parts in the Path
    • toString

      public String toString()
      Overrides:
      toString in class Object
      Returns:
      a pretty printed "/" separated path string representation
    • equals

      public boolean equals(Object o)
      Overrides:
      equals in class Object
      Returns:
      true of the objects string representations match
    • subpath

      public Path subpath(int fromIndex, int toIndex)
      Creates a new sub Path.
      Parameters:
      fromIndex - low endpoint (inclusive) of the subList
      toIndex - high endpoint (exclusive) of the subList
      Returns:
      a subpath from fromIndex (inclusive) to toIndex (exclusive)
    • isStatic

      public boolean isStatic(int index)
      Checks to see if the value at index is a wildcard, a variable, or is optional.
      Parameters:
      index - the path part to check
      Returns:
      true if the path part at index is a '*' char or starts with '[', '{' or ':'
    • isWildcard

      public boolean isWildcard(int index)
      Check if the path part at index is equal to '*' without having to check if size() @lt; index first.
      Parameters:
      index - the path part to check
      Returns:
      true if the path part at index
    • isVar

      public boolean isVar(int index)
      Check to see if the value at index starts with '${', '{', ':' after removing any leading '[' characters.
      Parameters:
      index - the index to check
      Returns:
      true if the value exists and is variableized but not a wildcard, false otherwise.
    • getVarName

      public String getVarName(int index)
      Extracts a variable name form the path expression if index exists and has a var name.

      'varName' would be extracted from getVarName(1) for the following paths.

      • /part/:varName/
      • /part/{varNam:regex}/
      • /part/${varNam:regex}/

      Square brackets indicating a path component is optioanl don't impact retrieval of the var name so the following would return the same as there above counterparts:

      • /part/[:varName]/
      • /[part]/{varNam:regex}]/
      • /[part]/[${varNam:regex}]/
      Parameters:
      index - the index of the var name to get
      Returns:
      the variable name binding for the parth part at index if it exists
    • isOptional

      public boolean isOptional(int index)
      Square brackets, '[]', indicate that a path path (and by implication, all following parts) are considered optional for path matching.

      For example: new Path("first/second/[third]/").matches(new Path("first/second/third"))

      Parameters:
      index - the part part to check
      Returns:
      true if the path part at index exists and starts with '[' and ends with ']'
    • matches

      public boolean matches(String toMatch)
      Convenience overloading of matches(Path).
      Parameters:
      toMatch - the path string to match
      Returns:
      true if the paths match
    • matches

      public boolean matches(Path concretePath)
      Checks if this Path is as case insensitive match, including any optional rules, wildcards, and regexes to concretePath.

      As the name implies concretePath is considered to be a literal path not containing optionals, wildcards, and regexes.

      As also documented above:

      • paths can end with a "/*" character meaning this and all following parts are optional and unconstrained
      • wrapping a path part in square brackets, '[]' indicates that it and all following part parts are optional.
      • wrapping a path part in '${}' or '${}' indicates that this path part should match via a regular expression such as '${[0-9a-fA-F]{1,8}}' to match a 1 to 8 character alpha numeric part
      • you can bind a variable name to a regex by preceding the regex with a name and a colon '/${id:[0-9a-fA-F]{1,8}}/'
      • starting a path part with a ':' such as '/:id/' is functionally equivalent to '/${id:.*}'

      All non regex comparisons are performed with String.equalsIgnoreCase.

      All regexes are compiled with Pattern.CASE_INSENSITIVE.

      Parameters:
      concretePath - the path to match against
      Returns:
      true if this path matches concretePath
    • extract

      public Path extract(Map params, Path toMatch)
      Convenience overloading of extract(Map, Path, boolean) with greedy = true.
      Parameters:
      params - a map to add extracted name/value pairs to
      toMatch - the path to extract from
      Returns:
      the part of this path that matched
      See Also:
    • extract

      public Path extract(Map params, Path matchingConcretePath, boolean greedy)
      Consumes the matching parts of matchingConcretePath and extracts any named variable to matchingConcretePath if this any of this paths parts contain variable bindings.

      If greedy is true, the match will consume through matching optional path parts.

      If greedy is false, variable bindings in any optional paths parts will be extracted but the parts will not be removed from matchingConcretePath.

      Here is an example:

         Map params = new HashMap();
         Path engineMatch = new Path("apis/myapi/*");
         Path apiMatch = new Path("${version:v1|v2}/:tenant")
         Path endpointMatch = new Path("[${collection:books|categories|orders}]/*");
         Path actionMatch = new Path("[:orderId]/*");
      
         Path requestPath = new Path("/apis/myapi/v2/bobsBooks/orders/67890");
      
         engineMatch.extract(requestPath);
         // params is empty, requestPath is now 'v2/bobsBooks/orders/67890'
      
         apiMatch.extract(requestPath);
         //version=v2 and tenant=bobsBooks have been added to params and requestPath is now 'orders/67890'
      
         endpointMatch.extract(requestPath);
         //collection=orders has been added to params and requestPath is now '67890'
      
         actionMatch.extract(requestPath);
         //orderId=67890 has been added to params and requestPath is now empty.
       

      The above example is very similar to how an Engine processes paths when selecting an Api, Endpoint, and Actions to run.

      Engine will also add the params to the Request Url params as if they had been supplied as name value pairs by the caller on the query string.

      Parameters:
      params - the map to add extracted name/value pairs to
      matchingConcretePath - the path to extract from
      greedy - if extraction should consume through optional path parts
      Returns:
      the same Path object that was passed in but potentially now shorter as matching segments may have been consumed
      See Also: