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

org.apache.wink.common.internal.uritemplate.UriTemplateMatcher Maven / Gradle / Ivy

There is a newer version: 1.4
Show newest version
/*******************************************************************************
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *  
 *   http://www.apache.org/licenses/LICENSE-2.0
 *  
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 *  
 *******************************************************************************/

package org.apache.wink.common.internal.uritemplate;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;

import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.PathSegment;

import org.apache.wink.common.internal.MultivaluedMapImpl;
import org.apache.wink.common.internal.i18n.Messages;
import org.apache.wink.common.internal.uri.UriEncoder;
import org.apache.wink.common.internal.uritemplate.UriTemplateProcessor.CapturingGroup;

public class UriTemplateMatcher {

    protected UriTemplateProcessor                    parent;
    protected String                                  uri;
    protected Matcher                                 matcher;
    protected boolean                                 matches;
    private MultivaluedMap            variables;
    private MultivaluedMap           variablesStartIndices;
    private MultivaluedMap> variablesPathSegments;

    @Override
    public String toString() {
        return String.format("Parent: %s; URI: %s; Matcher: %s; Matches: %b", //$NON-NLS-1$
                             parent,
                             uri,
                             matcher,
                             matches);
    }

    /* package */UriTemplateMatcher(UriTemplateProcessor parent) {
        this.parent = parent;
        this.uri = null;
        this.matcher = null;
        this.matches = false;
        this.variables = null;
        this.variablesStartIndices = null;
        this.variablesPathSegments = null;
    }

    /**
     * Get the {@link UriTemplateProcessor} that this matcher was created from
     * 
     * @return UriTemplateProcessor instance
     */
    public UriTemplateProcessor getProcessor() {
        return parent;
    }

    /**
     * Try to match a URI and return the variable values or null if it
     * does not match. Same as calling match(uri, false)
     * 
     * @param uri the URI
     * @return variable values or null if it does not match
     */
    public MultivaluedMap match(String uri) {
        return match(uri, false);
    }

    /**
     * Try to match a URI and return the variable values or null if it
     * does not match.
     * 
     * @param uri the URI
     * @param decode indicates whether to decode the values before returning
     *            them
     * @return variable values or null if it does not match
     */
    public MultivaluedMap match(String uri, boolean decode) {
        if (!matches(uri)) {
            return null;
        }
        return getVariables(decode);
    }

    /**
     * Match the provided uri against the uri template of this processor. If the
     * match was successful, then it is possible to get the variable values from
     * the matched uri.
     * 
     * @param uri the uri to match against the processor template
     * @return true if the uri matches the pattern, false otherwise
     */
    public boolean matches(String uri) {
        if (uri == null) {
            throw new NullPointerException(Messages.getMessage("variableIsNull", "uri")); //$NON-NLS-1$ //$NON-NLS-2$
        }
        this.uri = uri;
        this.variables = null;
        this.variablesStartIndices = null;
        this.variablesPathSegments = null;
        this.matcher = parent.getPattern().matcher(uri);
        return (this.matches = this.matcher.matches());
    }

    /**
     * Returns whether the last successful match was an exact match, that is, if
     * the matched tail part is empty.
     * 
     * @return true if the match was exact, false otherwise.
     */
    public boolean isExactMatch() {
        assertMatchState();
        String tail = getTail();
        return (tail == null || tail.length() == 0 || tail.equals("/")); //$NON-NLS-1$
    }

    /**
     * Get the decoded tail (literal) part of the last uri that matched the
     * template. This is mainly used for searching of sub-resources during
     * request dispatching. Same as calling getTail(true).
     * 
     * @return the decoded tail part of the last matched uri
     */
    public String getTail() {
        return getTail(true);
    }

    /**
     * Get the tail (literal) part of the last uri that matched the template.
     * This is mainly used for searching of sub-resources during request
     * dispatching.
     * 
     * @param decode indicates whether the tail should be decoded before
     *            returning
     * @return the tail part of the last matched uri
     */
    public String getTail(boolean decode) {
        assertMatchState();
        if (parent.tail == null) {
            return ""; //$NON-NLS-1$
        }

        String value = matcher.group(parent.tail.getCapturingGroupId());
        if (decode) {
            value = UriEncoder.decodeString(value);
        }
        return value;
    }

    /**
     * Get the decoded head part of the last uri that matched the template. Same
     * as calling getHead(true).
     * 
     * @return the decoded head part of the last matched uri
     */
    public String getHead() {
        return getHead(true);
    }

    /**
     * Get the head part of the last uri that matched the template.
     * 
     * @param decode indicates whether the head should be decoded before
     *            returning
     * @return the head part of the last matched uri
     */
    public String getHead(boolean decode) {
        assertMatchState();
        if (parent.head == null) {
            return uri;
        }

        String head = matcher.group(parent.head.getCapturingGroupId());
        if (decode) {
            head = UriEncoder.decodeString(head);
        }

        // if the template ends with a "/" and the tail is "/",
        // then add it to the head because the tail has caught it but it
        // should be part of the head
        String tail = getTail(false);
        if (parent.template.endsWith("/") && tail != null && tail.equals("/")) { //$NON-NLS-1$ //$NON-NLS-2$
            head += tail;
        }
        return head;
    }

    /**
     * Get the decoded value of the specified template variable from the last
     * matched uri. If there is more than one value, then the first value is
     * returned. Same as calling getVariableValue(name, true)
     * 
     * @param name the name of the template variable to get the value for
     * @return the variable value or null if it does not exist in the template
     * @throws IllegalStateException if the last match failed
     */
    public String getVariableValue(String name) throws IllegalStateException {
        return getVariableValue(name, true);
    }

    /**
     * Get the value of the specified template variable from the last matched
     * uri. If there is more than one value, then the first value is returned.
     * 
     * @param name the name of the template variable to get the value for
     * @param decode indicates whether the value should be decoded
     * @return the variable value or null if it does not exist in
     *         the template
     * @throws IllegalStateException if the last match failed
     */
    public String getVariableValue(String name, boolean decode) throws IllegalStateException {
        List list = getVariableValues(name, decode);
        if (list.size() == 0) {
            return null;
        }

        return list.get(0);
    }

    /**
     * Get the decoded values of the specified template variable from the last
     * matched uri. Same as calling getVariableValues(name, true)
     * 
     * @param name the name of the template variable to get the value for
     * @return a list of variable values
     * @throws IllegalStateException if the last match failed
     */
    public List getVariableValues(String name) throws IllegalStateException {
        return getVariableValues(name, true);
    }

    /**
     * Get the values of the specified template variable from the last matched
     * uri.
     * 
     * @param name the name of the template variable to get the value for
     * @param decode indicates whether the values should be decoded
     * @return a list of variable values
     * @throws IllegalStateException if the last match failed
     */
    public List getVariableValues(String name, boolean decode) throws IllegalStateException {
        if (name == null) {
            return new ArrayList();
        }
        MultivaluedMap variables = getVariables(decode);
        List values = variables.get(name);
        if (values == null) {
            return new ArrayList();
        }
        return values;
    }

    /**
     * Get a multivalued map of the template variables and their values from the
     * last matched uri. Same as calling getVariables(false)
     * 
     * @return a map of variables and their values from the last matched uri
     * @throws IllegalStateException if the last match failed
     */
    public MultivaluedMap getVariables() throws IllegalStateException {
        return getVariables(false);
    }

    /**
     * Get a multivalued map of the template variables and their values from the
     * last matched uri.
     * 
     * @param decode indicates whether the values should be decoded
     * @return a map of variables and their values from the last matched uri
     * @throws IllegalStateException if the last match failed
     */
    public MultivaluedMap getVariables(boolean decode) throws IllegalStateException {
        return storeVariables(null, decode);
    }

    /**
     * Get a multivalued map of the template variables and their values from the
     * last matched uri
     * 
     * @param out an output multivalued map to receive the values of variables.
     *            If null, a new instance is created. This map is also the
     *            return value.
     * @param decode indicates whether the values should be decoded
     * @return the multivalued map of variables and their values from the last
     *         matched uri
     * @throws IllegalStateException if the last match failed
     */
    public MultivaluedMap storeVariables(MultivaluedMap out,
                                                         boolean decode)
        throws IllegalStateException {

        assertMatchState();

        buildVariables();

        if (out == null) {
            out = new MultivaluedMapImpl();
        }

        MultivaluedMapImpl.addAll(variables, out);

        if (decode) {
            decodeValues(out);
        }

        return out;
    }

    /**
     * Store the path segments that are associated with each matched variable
     * into the output map
     * 
     * @param uriSegments the full list of the original request uri segments
     *            (including any matrix parameters)
     * @param offset the offset into the segments list to take the matched
     *            variable segments from
     * @param count the number of segments from the specified offset
     * @param out an output multivalued map to receive the path segments of the
     *            variables. If null, a new instance is created. This map is
     *            also the return value.
     * @return the multivalued map of variables path segments from the last
     *         matched uri
     * @throws IllegalStateException if the last match failed
     */
    public MultivaluedMap> storeVariablesPathSegments(List segments,
                                                                                int offset,
                                                                                int count,
                                                                                MultivaluedMap> out)
        throws IllegalStateException {
        assertMatchState();

        buildVariablesPathSegments(segments, offset, count);

        if (out == null) {
            out = new MultivaluedMapImpl>();
        }

        MultivaluedMapImpl.addAll(variablesPathSegments, out);

        return out;
    }

    /**
     * extract the values of the matched variables
     */
    private void buildVariables() {
        if (variables != null) {
            return;
        }
        variables = new MultivaluedMapImpl();
        variablesStartIndices = new MultivaluedMapImpl();
        MultivaluedMap variableGroups = parent.getVariables();
        // go over all the variables
        for (String name : variableGroups.keySet()) {
            // go over all of the template variables that have this name
            List vars = variableGroups.get(name);
            for (CapturingGroup var : vars) {
                // retrieve the capturing group that is associated with this
                // variable
                int group = var.getCapturingGroupId();
                // get the value that was captured during the last match and
                // the start index of the matched string
                String matched = matcher.group(group);
                int startIndex = matcher.start(group);
                // fire the 'onMatch' event for the variable
                var.onMatch(matched, variables, startIndex, variablesStartIndices);
            }
        }
    }

    /**
     * builds the list of path segments that are associated with every variable
     * 
     * @param segments the list of path segments of the original uri used for
     *            the matching
     * @param offset the offset into the segments list to take the matched
     *            variable segments from
     * @param count the number of segments from the specified offset
     */
    private void buildVariablesPathSegments(List segments, int offset, int count) {
        if (variablesPathSegments != null) {
            return;
        }

        buildVariables();

        variablesPathSegments = new MultivaluedMapImpl>();

        // this method finds the path segments that every variable is part of in
        // the following way:
        // for every value of every variable, use the start and end indices of
        // the matched value
        // to calculate in which segments the matched variable falls into.

        // go over all of the variables
        for (String name : variables.keySet()) {
            // go over all of the values of each variable
            for (int i = 0; i < variables.get(name).size(); ++i) {
                String variableValue = variables.get(name).get(i);
                if (variableValue == null) {
                    // aggressive safety
                    continue;
                }

                int variableValueStartIndex = variablesStartIndices.get(name).get(i);
                List variableSegments = new LinkedList();
                int pathLength = 0;
                // go over all of the segments of the request uri that was
                // matched
                L1: for (int segmentIndex = offset; segmentIndex < offset + count; ++segmentIndex) {
                    pathLength += segments.get(segmentIndex).getPath().length();
                    if (variableValueStartIndex < pathLength) {
                        // found the segment that the variable value starts in.
                        // now we need to find how many segments this variable
                        // spans across.
                        int lastSegmentIndex = segmentIndex;
                        int variableValueEndIndex =
                            variableValueStartIndex + variableValue.length() - 1;
                        // find the segment that this matched variable value
                        // ends in
                        while (variableValueEndIndex > pathLength) {
                            ++lastSegmentIndex;
                            // + 1 is to count for the '/' between the segments
                            pathLength += segments.get(lastSegmentIndex).getPath().length() + 1;
                        }
                        // copy all the segments that the variable spans across
                        // to the output list
                        for (; segmentIndex <= lastSegmentIndex; ++segmentIndex) {
                            variableSegments.add(segments.get(segmentIndex));
                        }
                        break L1; // found all the segments of this variable
                        // value
                    } else if (variableValueStartIndex == pathLength) {
                        // no PathParam was provided... only matrix params or
                        // empty
                        // just use what we have
                        variableSegments.add(segments.get(segmentIndex));
                        break L1;
                    } else {
                        pathLength += 1; // to count for the '/' between the
                        // segments
                    }
                }
                variablesPathSegments.add(name, variableSegments);
            }
        }
    }

    // private int getVariableStartIndex(String name) throws
    // IllegalStateException {
    // if (name == null) {
    // throw new NullPointerException("name");
    // }
    // assertMatchState();
    //
    // MultivaluedMap variables = parent.getVariables();
    // List vars = variables.get(name);
    // if (vars == null || vars.size() == 0) {
    // return -1;
    // }
    //
    // CapturingGroup capGroup = vars.get(vars.size() - 1);
    // return matcher.start(capGroup.getCapturingGroupId());
    // }
    //
    protected MultivaluedMap decodeValues(MultivaluedMap values) {
        for (List list : values.values()) {
            for (int i = 0; i < list.size(); ++i) {
                list.set(i, UriEncoder.decodeString(list.get(i)));
            }
        }
        return values;
    }

    protected void assertMatchState() {
        if (!matches) {
            throw new IllegalStateException(Messages.getMessage("lastMatchWasUnsuccessful")); //$NON-NLS-1$
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy