Class Path

java.lang.Object
io.inversion.utils.Path
All Implemented Interfaces:
Comparable<Path>

public class Path extends Object implements Comparable<Path>
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]/*")))));
 
  • 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

    • getTemplate

      public String getTemplate()
    • expandOptionals

      public static List<Path> expandOptionals(List<Path> paths)
    • filterDuplicates

      public static List<Path> filterDuplicates(List<Path> paths)
    • materializeTrivialRegexes

      public static List<Path> materializeTrivialRegexes(List<Path> paths)
    • copyFrom

      public Path copyFrom(Path path)
    • copy

      public Path copy()
      Return a new path that is an exactly copy of this one.
      Returns:
    • 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 Path 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
    • set

      public Path set(int index, String part)
    • chop

      public Path chop()
    • 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
    • removeLast

      public Path removeLast()
    • removeTrailingWildcard

      public boolean removeTrailingWildcard()
      Returns:
      true if this Path ended in a "*" which was removed, false if this Path does not end in a "*"
    • endsWithWildcard

      public boolean endsWithWildcard()
      Returns:
      true if this Path ends with a "*"
    • 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
    • hashCode

      public int hashCode()
      Overrides:
      hashCode in class Object
    • compareTo

      public int compareTo(Path o)
      Specified by:
      compareTo in interface Comparable<Path>
    • equals

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

      public static String unwrapOptional(String part)
    • 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
    • isWildcard

      public static boolean isWildcard(String part)
    • isWildcard

      public boolean isWildcard()
      Returns:
      true if this path equals "*"
    • 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.
    • isVar

      public static boolean isVar(String part)
    • 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
    • getRegex

      public String getRegex(int index)
    • getRegex

      public static String getRegex(String part)
    • 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 ']'
    • isOptional

      public static boolean isOptional(String part)
    • setOptional

      public void setOptional(int index, boolean optional)
    • 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 toMatch)
      Checks if this Path is a 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:
      toMatch - the path to match against
      Returns:
      true if this path matches concretePath
    • matches

      public boolean matches(Path toMatch, boolean bidirectional)
    • 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.
       

      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
    • getVarIndex

      public int getVarIndex(String varName)
    • hasVars

      public boolean hasVars()
    • hasAllVars

      public boolean hasAllVars(String... vars)
    • hasAnyVars

      public boolean hasAnyVars(String... vars)
    • getSubPaths

      public List<Path> getSubPaths()
      Creates a list of all subpaths breaking before each optional segment Ex:
           a/b[c]/d/[e]/*
       
      Becomes:
           a/b
           a/b/c/d
           a/b/c/d/e/*
       
      Returns:
      a list of all valid paths