All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.higgs.core.ResourcePath Maven / Gradle / Ivy

package io.higgs.core;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * A resource path is the location where an end point is accessible.
 * For e.g. test/{param:[a-z0-9]} means a resource is available at /test/var
 * where var can be any letter a-z or number 0-9 AND param is accessible by the method
 * to be injected on invocation.
 * A resource's path can have any number of parameters. Parameters are contained withing curly braces {},
 * parameter names are any valid string and parameters can optionally have a regex pattern.
 * If a regex pattern is available it is separated from the parameter name by a colon. All characters after the colon,
 * including any whitespace is considered part of the regex pattern.
 * If no regex is given then the default "[^/]+?" is used.
 * Like a normal URI components are separated with forward slash "/".
 * A parameterized component cannot be mixed with a string component. e.g. the following is invalid
 * /test/abc{param}/123 but the following is valid
 * /test/{param:abc}/123
 *
 * @author Courtney Robinson 
 */
public class ResourcePath {
    private final String uri;
    private Component[] components;
    private Map namedComponents = new HashMap<>();

    public ResourcePath(final String uri) {
        this.uri = uri;
        parsePath();
    }

    private void parsePath() {
        String[] parts = uri.split("/");
        components = new Component[parts.length];
        int index = 0;
        for (String part : parts) {
            if (part.isEmpty()) {
                //shrink array by 1
                components = Arrays.copyOf(components, components.length - 1, Component[].class);
                continue;
            }
            Component component = new Component();
            component.setComponentValue(part);
            if (part.startsWith("{") && part.endsWith("}")) {
                int colonIndex = part.indexOf(':');
                if (colonIndex != -1) {
                    //named parameter (minus the {})
                    String name = part.substring(1, colonIndex);
                    //pattern is everything from : to } (exclusive of both)
                    String pattern = part.substring(colonIndex + 1, part.length() - 1);
                    component.setName(name);
                    component.setPattern(pattern);
                } else {
                    //no colon the whole thing is a parameter name (minus the {})
                    String name = part.substring(1, part.length() - 1);
                    component.setName(name);
                    component.setPattern("[^/]+?");
                }
                namedComponents.put(component.getName(), component);
            }
            components[index] = component;
            index++;
        }
    }

    public String getUri() {
        return uri;
    }

    public Component[] getComponents() {
        return components;
    }

    /**
     * Checks if the given path matches this resource path.
     * A string path matches if the following conditions hold true.
     * 
     *     1) The string path, when split into its components has the same length as
     *          {@link #getComponents()}.length
     *     2) If this resource path contains dynamic components (regex),
     *          each regex must match the component(at the same index) in the string path
     * 
* NOTE: if a query string is included in the string path given it will be stripped and not * included in the comparison. e.g. /home/me/edit?id=123 will become /home/me/edit * * @param path the name/path/url to match against * @return */ public boolean matches(String path) { path = path.trim(); //since we split on / a root path would create an empty component array // so do a manual check against the raw string uri that created this path if (path.equalsIgnoreCase("/") && uri.equalsIgnoreCase("/")) { return true; } int qIndex = path.indexOf('?'); if (qIndex != -1) { //remove query string from path path = path.substring(0, qIndex); } String[] parts = path.split("/"); //use size to avoid re-sizing array on empty components and instead padding with null //in these cases parts.length is unreliable for comparison int size = parts.length; //first make sure empty components are removed, in cases where a URL contains something like some//path/eg for (int i = 0; i < parts.length; i++) { String part = parts[i].trim(); if (part.isEmpty()) { //set to null (can skip later if null) and -1 from size parts[i] = null; --size; } } if (components.length != size) { return false; } //iterate over the parts, as soon as the first component doesn't match return false int i = 0; for (int j = 0; j < parts.length; j++) { //skip, we set it to null above if (parts[j] == null) { continue; } String component = parts[j]; Component pathComponent = components[i]; if (!pathComponent.isPattern()) { //if its not a pattern the strings must be equal if (!component.equalsIgnoreCase(pathComponent.getComponentValue())) { return false; } } else { //if its a pattern the string must match if (!pathComponent.matches(component)) { return false; } } //if we get here then the path component matched the component value //so the path's runtime component value should be the extracted component pathComponent.setRuntimeValue(component); i++; } //if we get here all the components matched return true; } /** * Gets a named component from the resource's path or null if not found * * @param name the name of the component to return * @return */ public Component getComponent(final String name) { for (Component component : components) { if (component != null && component.isNamed() && name.equals(component.getName())) { return component; } } return null; } @Override public String toString() { StringBuilder b = new StringBuilder(); for (Component c : components) { b.append(c.getComponentValue()).append("/"); } return "ResourcePath{" + "uri='" + b.toString() + '\'' + '}'; } public static class Component { private String componentValue; private boolean isPattern; private String name; private String runtimeValue; private Pattern pattern; /** * Check if this component is named or not * * @return */ public boolean isNamed() { return name != null; } /** * Get the value that was parsed out of the uri comparison which resulted in this instance * * @return */ public String getRuntimeValue() { return runtimeValue; } /** * Set the runtime component value of a path component. This value goes with {@link #getName()} to * make a key value pair. Wherever {@link #getName()} is used as a parameter the value set with * this method will be injected in its place * * @param runtimeValue */ public void setRuntimeValue(final String runtimeValue) { this.runtimeValue = runtimeValue; } public String getComponentValue() { return componentValue; } public void setComponentValue(final String value) { this.componentValue = value; } public boolean isPattern() { return isPattern; } public String getName() { return name; } public void setName(final String name) { this.name = name; } public Pattern getPattern() { return pattern; } public void setPattern(final boolean pattern) { isPattern = pattern; } public void setPattern(final String pattern) { this.pattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE | Pattern.DOTALL); setPattern(true); } /** * @param component the string component to compare this path component to * @return true if the path's patter matches the given string, false otherwise */ public boolean matches(final String component) { Matcher matcher = pattern.matcher(component); return matcher.matches(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy