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

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

The 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.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.ws.rs.core.MultivaluedMap;

import org.apache.wink.common.internal.i18n.Messages;
import org.apache.wink.common.internal.uri.UriEncoder;
import org.apache.wink.common.internal.utils.StringUtils;

/**
 * BitWorking style
 * template processor for compiling, matching and expanding URI templates
 */
public class BitWorkingUriTemplateProcessor extends UriTemplateProcessor {

    /*
     * op = 1ALPHA arg =(reserved / unreserved / pct-encoded) varname = (ALPHA /
     * DIGIT)(ALPHA / DIGIT / "." / "_" / "-" ) vardefault =(unreserved /
     * pct-encoded) var = varname [ "=" vardefault ] vars = var [("," var) ]
     * operator = "-" op "|" arg "|" vars expansion = "{" ( var / operator ) "}"
     */
    private static final String  HEX                         = "[0-9A-Fa-f]";                       //$NON-NLS-1$
    private static final String  RESERVED                    = "[;/?:@&=+$,]";                      //$NON-NLS-1$
    private static final String  UNRESERVED                  = "[\\w\\.!~*'()-]";                   //$NON-NLS-1$
    private static final String  PCT_ENCONDED                = "(?:%" + HEX + HEX + ")";            //$NON-NLS-1$ //$NON-NLS-2$
    private static final String  ALPHA                       = "[a-zA-Z]";                          //$NON-NLS-1$

    private static final String  BITWORKING_OP               = "(" + ALPHA + "+)";                  //$NON-NLS-1$ //$NON-NLS-2$
    private static final String  BITWORKING_ARG              = "((?:" + RESERVED //$NON-NLS-1$
                                                                 + "|" //$NON-NLS-1$
                                                                 + UNRESERVED
                                                                 + "|" //$NON-NLS-1$
                                                                 + PCT_ENCONDED
                                                                 + ")*)";                           //$NON-NLS-1$
    private static final String  BITWORKING_VARNAME          = "\\w[\\w\\.-]*";                     //$NON-NLS-1$
    private static final String  BITWORKING_VARDEFAULT       = "((?:" + UNRESERVED //$NON-NLS-1$
                                                                 + "|" //$NON-NLS-1$
                                                                 + PCT_ENCONDED
                                                                 + ")*)";                           //$NON-NLS-1$
    private static final String  BITWORKING_VAR              = "(" + BITWORKING_VARNAME //$NON-NLS-1$
                                                                 + ")(?:=" //$NON-NLS-1$
                                                                 + BITWORKING_VARDEFAULT
                                                                 + ")?";                            //$NON-NLS-1$
    private static final String  BITWORKING_VARS             = "(" + BITWORKING_VAR //$NON-NLS-1$
                                                                 + "(?:," //$NON-NLS-1$
                                                                 + BITWORKING_VAR
                                                                 + ")*)";                           //$NON-NLS-1$
    private static final String  BITWORKING_OPERATOR         = "(?:-" + BITWORKING_OP //$NON-NLS-1$
                                                                 + "[|]" //$NON-NLS-1$
                                                                 + BITWORKING_ARG
                                                                 + "[|]" //$NON-NLS-1$
                                                                 + BITWORKING_VARS
                                                                 + ")";                             //$NON-NLS-1$
    private static final String  BITWORKING_EXPANSION        = "\\{(?:" + BITWORKING_VAR //$NON-NLS-1$
                                                                 + "|" //$NON-NLS-1$
                                                                 + BITWORKING_OPERATOR
                                                                 + ")\\}";                          //$NON-NLS-1$
    private static final Pattern BITWORKING_VARIABLE_PATTERN =
                                                                 Pattern
                                                                     .compile(BITWORKING_EXPANSION);

    /**
     * Create a processor without a template
     */
    public BitWorkingUriTemplateProcessor() {
        super();
    }

    /**
     * Create an processor with the provided template. The
     * {@link #compile(String)} method is called on the provided template.
     * 
     * @param template the template that this processor is associated with
     */
    public BitWorkingUriTemplateProcessor(String template) {
        this();
        compile(template);
    }

    @Override
    public final void compile(String uriTemplate) {
        compile(uriTemplate, new BitWorkingPatternBuilder(this));
    }

    /**
     * Compile the provided uri template and pass compilation events to the
     * provided {@link BitWorkingCompilationHandler}.
     * 
     * @param template the template to compile
     * @param handler the CompilationHandler that will receive compilation
     *            events
     */
    public static void compile(String template, BitWorkingCompilationHandler handler) {
        if (template == null) {
            throw new NullPointerException(Messages.getMessage("variableIsNull", "template")); //$NON-NLS-1$ //$NON-NLS-2$
        }
        if (handler == null) {
            throw new NullPointerException(Messages.getMessage("variableIsNull", "handler")); //$NON-NLS-1$ //$NON-NLS-2$
        }

        int start = 0;
        String literal = ""; //$NON-NLS-1$

        // fire start
        handler.startCompile(template);

        Matcher matcher = BITWORKING_VARIABLE_PATTERN.matcher(template);
        while (matcher.find()) {
            // get the literal part which is the characters up to the variable
            literal = template.substring(start, matcher.start());
            start = matcher.end();

            // fire literal
            handler.literal(literal);

            // get the different parts according to the following:
            // group 1: variable name
            // group 2: variable default value
            // group 3: operator name
            // group 4: operator arg
            // group 5: operator vars
            // if a variable was matched then all the operator groups will be
            // null
            // if an operator was matched then all the variable groups will be
            // null

            String variable = matcher.group(1);
            if (variable != null) {
                // variable
                String defaultValue = matcher.group(2);
                // fire variable
                handler.variable(variable, defaultValue);
            } else {
                // operator
                String operator = matcher.group(3);
                String arg = matcher.group(4);
                String vars = matcher.group(5);

                // extract the variable names of the operator
                String[] arrayVars = StringUtils.fastSplit(vars, ","); //$NON-NLS-1$
                Map varsMap = new LinkedHashMap();
                for (String var : arrayVars) {
                    String defaultValue = null;
                    int index = var.indexOf('=');
                    // is there a default value?
                    if (index != -1) {
                        defaultValue = var.substring(index + 1);
                        var = var.substring(0, index);
                    }
                    varsMap.put(var, defaultValue);
                }

                // fire operator
                handler.operator(operator, arg, varsMap);
            }
        }
        // get the trailing literal part
        literal = template.substring(start);

        // fire end
        handler.endCompile(literal);
    }

    /**
     * Expand the provided template using the provided values. Regular
     * expressions of variables in the template are ignored. All variables
     * defined in the template must have a value.
     * 
     * @param template the uri template to expand
     * @param values a map with the values of the variables
     * @return an expanded uri using the supplied variable values
     */
    public static String expand(String template, MultivaluedMap values) {
        if (template == null) {
            return null;
        }
        StringBuilder result = new StringBuilder();
        expand(template, values, result);
        return result.toString();
    }

    /**
     * Expand the provided template using the provided values. Regular
     * expressions of variables in the template are ignored. All variables
     * defined in the template must have a value.
     * 
     * @param template the uri template to expand
     * @param values a map with the values of the variables
     * @param out the output for the expansion
     */
    public static void expand(String template,
                              MultivaluedMap values,
                              StringBuilder out) {
        if (template == null) {
            return;
        }
        BitWorkingTemplateExpander expander = new BitWorkingTemplateExpander(values, out);
        compile(template, expander);
    }

    /**
     * Factory method for normalized uri-templates.
     * 
     * @param uriTemplate uri-template specification
     * @return instance representing (normalized) uri-template
     * @see UriTemplateProcessor#normalizeUri(String)
     */
    public static UriTemplateProcessor newNormalizedInstance(String uriTemplate) {
        return new BitWorkingUriTemplateProcessor(UriTemplateProcessor.normalizeUri(uriTemplate));
    }

    /**
     * This interface is used for receiving events during the compilation of a
     * uri template.
     * 
     * @see {@link BitWorkingUriTemplateProcessor#compile(String, BitWorkingCompilationHandler)}
     */
    public static interface BitWorkingCompilationHandler extends BaseCompilationHandler {
        /**
         * Variable event.
         * 
         * @param name the name of the variable
         * @param defaultValue the default value of the variable, or null
         */
        public void variable(String name, String defaultValue);

        /**
         * Operator event.
         * 
         * @param name the name of the operator
         * @param arg the argument of the operator
         * @param vars a map of variables and their default values
         */
        public void operator(String name, String arg, Map vars);

    }

    /**
     * This compilation handler handles the compilation events for compiling the
     * uri template of a processor instance. It creates the regex pattern to use
     * for matching and the variables used for matching and expansion, and then
     * sets them on the processor instance.
     */
    private static class BitWorkingPatternBuilder extends AbstractPatternBuilder implements
        BitWorkingCompilationHandler {

        public BitWorkingPatternBuilder(BitWorkingUriTemplateProcessor processor) {
            super(processor);
        }

        public void variable(String name, String defaultValue) {
            // create a new variable
            CapturingGroup variable = createVariable(name, null, defaultValue);
            // save it to the map of capturing variables for use during matching
            processor.variables.add(name, variable);
            // save it for use during expansion
            processor.expanders.add(variable);
        }

        public void operator(String name, String arg, Map vars) {
            // get a new operator
            BitWorkingOperator operator = BitWorkingOperator.forName(name);
            if (operator == null) {
                throw new IllegalArgumentException(Messages.getMessage("unsupportedOperator", name)); //$NON-NLS-1$
            }
            // set the arg and vars of the operator
            operator.setArg(arg);
            operator.setVars(vars);
            // build it into the pattern
            operator.build(patternBuilder);
            // set its capturing group
            ++capturingGroupId;
            operator.setCapturingGroupId(capturingGroupId);

            // save it to the map of capturing variables for use during matching
            for (String var : vars.keySet()) {
                processor.variables.add(var, operator);
            }

            // save it for use during expansion
            processor.expanders.add(operator);
        }
    }

    /**
     * Represents a BitWorking operator
     */
    private abstract static class BitWorkingOperator extends CapturingGroup {
        protected String              name;
        protected String              arg;
        protected Map vars;

        protected BitWorkingOperator(String name) {
            super();
            this.name = name;
            this.arg = null;
            this.vars = null;
        }

        public String getName() {
            return name;
        }

        public String getArg() {
            return arg;
        }

        public void setArg(String arg) {
            this.arg = arg;
        }

        public Map getVars() {
            return vars;
        }

        public void setVars(Map vars) {
            this.vars = vars;
        }

        /**
         * Return an instance of an operator for the specified name
         * 
         * @param name the name of the operator
         * @return an instance of an operator
         */
        public static BitWorkingOperator forName(String name) {
            if (name == null) {
                return null;
            }
            if (name.equals("neg")) { //$NON-NLS-1$
                return new Neg();
            }
            if (name.equals("opt")) { //$NON-NLS-1$
                return new Opt();
            }
            if (name.equals("prefix")) { //$NON-NLS-1$
                return new Prefix();
            }
            if (name.equals("suffix")) { //$NON-NLS-1$
                return new Suffix();
            }
            if (name.equals("list")) { //$NON-NLS-1$
                return new List();
            }
            if (name.equals("join")) { //$NON-NLS-1$
                return new Join();
            }
            return null;
        }

        /**
         * Represents the "neg" operator
         */
        private static class Neg extends BitWorkingOperator {
            public Neg() {
                super("neg"); //$NON-NLS-1$
            }

            public void build(StringBuilder builder) {
                builder.append("("); //$NON-NLS-1$
                builder.append(Pattern.quote(arg));
                builder.append(")?"); //$NON-NLS-1$
            }

            @Override
            public void onMatch(String matched,
                                MultivaluedMap values,
                                int startIndex,
                                MultivaluedMap indices) {
                // do nothing
            }

            public void expand(MultivaluedMap values,
                               boolean encode,
                               StringBuilder builder) {
                for (String var : vars.keySet()) {
                    if (values.containsKey(var) && values.get(var).size() > 0) {
                        return;
                    }
                }
                builder.append(arg);
            }
        }

        /**
         * Represents the "opt" operator
         */
        private static class Opt extends BitWorkingOperator {
            public Opt() {
                super("opt"); //$NON-NLS-1$
            }

            public void build(StringBuilder builder) {
                builder.append("("); //$NON-NLS-1$
                builder.append(Pattern.quote(arg));
                builder.append(")?"); //$NON-NLS-1$
            }

            @Override
            public void onMatch(String matched,
                                MultivaluedMap values,
                                int startIndex,
                                MultivaluedMap indices) {
                // do nothing
            }

            public void expand(MultivaluedMap values,
                               boolean encode,
                               StringBuilder builder) {
                for (String var : vars.keySet()) {
                    if (values.containsKey(var) && values.get(var).size() > 0) {
                        builder.append(arg);
                        return;
                    }
                }
            }
        }

        /**
         * Represents the "prefix" operator
         */
        private static class Prefix extends BitWorkingOperator {
            public Prefix() {
                super("prefix"); //$NON-NLS-1$
            }

            public void build(StringBuilder builder) {
                if (vars.size() != 1) {
                    throw new IllegalArgumentException(Messages
                        .getMessage("prefixOperatorMustHaveOnlyOneVariable")); //$NON-NLS-1$
                }

                builder.append("((?:"); //$NON-NLS-1$
                builder.append(Pattern.quote(arg));
                builder.append(REGEX0);
                builder.append(")*)"); //$NON-NLS-1$
            }

            @Override
            public void onMatch(String matched,
                                MultivaluedMap values,
                                int startIndex,
                                MultivaluedMap indices) {
                String var = vars.keySet().iterator().next();
                if (matched == null || matched.length() == 0) {
                    values.putSingle(var, null);
                    indices.putSingle(var, startIndex);
                    return;
                }

                if (!matched.startsWith(arg)) {
                    throw new IllegalArgumentException(Messages
                        .getMessage("matchedSuffixMustStartWith", arg)); //$NON-NLS-1$
                }

                // clear the previous values
                values.put(var, null);
                String[] array = StringUtils.fastSplit(matched, arg);
                // array[0] will always contain the empty string, so skip it
                for (int i = 1; i < array.length; ++i) {
                    values.add(var, array[i]);
                    indices.add(var, startIndex);
                }
            }

            public void expand(MultivaluedMap values,
                               boolean encode,
                               StringBuilder builder) {
                // we have only one var
                String var = vars.keySet().iterator().next();
                java.util.List varValues = values.get(var);
                if (varValues != null) {
                    for (String value : varValues) {
                        builder.append(arg);
                        if (encode) {
                            value = UriEncoder.encodeString(value);
                        }
                        builder.append(value);
                    }
                }
            }
        }

        /**
         * Represents the "suffix" operator
         */
        private static class Suffix extends BitWorkingOperator {
            public Suffix() {
                super("suffix"); //$NON-NLS-1$
            }

            public void build(StringBuilder builder) {
                if (vars.size() != 1) {
                    throw new IllegalArgumentException(Messages
                        .getMessage("suffixOperatorMustOnlyHaveOneVariable")); //$NON-NLS-1$
                }
                builder.append("((?:"); //$NON-NLS-1$
                builder.append(REGEX0);
                builder.append(Pattern.quote(arg));
                builder.append(")*)"); //$NON-NLS-1$
            }

            @Override
            public void onMatch(String matched,
                                MultivaluedMap values,
                                int startIndex,
                                MultivaluedMap indices) {
                String var = vars.keySet().iterator().next();
                if (matched == null || matched.length() == 0) {
                    values.putSingle(var, null);
                    indices.putSingle(var, startIndex);
                    return;
                }

                if (!matched.endsWith(arg)) {
                    throw new IllegalArgumentException(Messages
                        .getMessage("matchedSuffixMustEndWith", arg)); //$NON-NLS-1$
                }

                // clear the previous values
                values.put(var, null);
                String[] array = StringUtils.fastSplit(matched, arg);
                // the last element in the array will always contain the empty
                // string, so skip it
                for (int i = 0; i < array.length - 1; ++i) {
                    values.add(var, array[i]);
                    indices.add(var, startIndex);
                }
            }

            public void expand(MultivaluedMap values,
                               boolean encode,
                               StringBuilder builder) {
                // we have only one var
                String var = vars.keySet().iterator().next();
                java.util.List varValues = values.get(var);
                if (varValues != null) {
                    for (String value : varValues) {
                        if (encode) {
                            value = UriEncoder.encodeString(value);
                        }
                        builder.append(value);
                        builder.append(arg);
                    }
                }
            }
        }

        /**
         * Represents the "list" operator
         */
        private static class List extends BitWorkingOperator {
            public List() {
                super("list"); //$NON-NLS-1$
            }

            public void build(StringBuilder builder) {
                if (vars.size() != 1) {
                    throw new IllegalArgumentException(Messages
                        .getMessage("listOperatorMustHaveOnlyOneVariable")); //$NON-NLS-1$
                }
                builder.append("("); //$NON-NLS-1$
                builder.append(REGEX0);
                builder.append("(?:"); //$NON-NLS-1$
                builder.append(Pattern.quote(arg));
                builder.append(REGEX0);
                builder.append(")*)"); //$NON-NLS-1$
            }

            @Override
            public void onMatch(String matched,
                                MultivaluedMap values,
                                int startIndex,
                                MultivaluedMap indices) {
                String var = vars.keySet().iterator().next();

                if (matched == null || matched.length() == 0) {
                    values.putSingle(var, ""); //$NON-NLS-1$
                    indices.putSingle(var, startIndex);
                    return;
                }

                // clear the previous values
                values.put(var, null);
                String[] array = StringUtils.fastSplit(matched, arg);
                for (int i = 0; i < array.length; ++i) {
                    values.add(var, array[i]);
                    indices.add(var, startIndex);
                }
            }

            public void expand(MultivaluedMap values,
                               boolean encode,
                               StringBuilder builder) {
                // we have only one var
                String var = vars.keySet().iterator().next();
                String delim = ""; //$NON-NLS-1$
                java.util.List varValues = values.get(var);
                if (varValues != null) {
                    for (String value : varValues) {
                        builder.append(delim);
                        if (encode) {
                            value = UriEncoder.encodeString(value);
                        }
                        builder.append(value);
                        delim = arg;
                    }
                }
            }
        }

        /**
         * Represents the "join" operator
         */
        private static class Join extends BitWorkingOperator {
            public Join() {
                super("join"); //$NON-NLS-1$
            }

            public void build(StringBuilder builder) {
                String orSign = ""; //$NON-NLS-1$
                StringBuilder keysAndValuesPattern = new StringBuilder();
                keysAndValuesPattern.append("(?:"); //$NON-NLS-1$
                for (String var : vars.keySet()) {
                    keysAndValuesPattern.append(orSign);
                    keysAndValuesPattern.append("(?:"); //$NON-NLS-1$
                    keysAndValuesPattern.append(var);
                    keysAndValuesPattern.append("="); //$NON-NLS-1$
                    keysAndValuesPattern.append(REGEX0);
                    keysAndValuesPattern.append(")"); //$NON-NLS-1$
                    orSign = "|"; //$NON-NLS-1$
                }
                keysAndValuesPattern.append(")"); //$NON-NLS-1$

                // with a capturing group
                builder.append("("); //$NON-NLS-1$
                builder.append(keysAndValuesPattern.toString());
                builder.append("(?:"); //$NON-NLS-1$
                builder.append(Pattern.quote(arg));
                builder.append(keysAndValuesPattern.toString());
                builder.append(")*)?"); //$NON-NLS-1$
            }

            @Override
            public void onMatch(String matched,
                                MultivaluedMap values,
                                int startIndex,
                                MultivaluedMap indices) {
                // extract all the keys and values and prepare a temporary map
                Map extractedValues = new HashMap();
                if (matched != null) {
                    String[] array = StringUtils.fastSplit(matched, arg);
                    for (int i = 0; i < array.length; ++i) {
                        String var = array[i];
                        String value = ""; //$NON-NLS-1$
                        int index = var.indexOf('=');
                        if (index != -1) {
                            value = var.substring(index + 1);
                            var = var.substring(0, index);
                        }
                        extractedValues.put(var, value);
                    }
                }

                // add all the values to the output multivalued map.
                // if a certain key did not have a value, then null will added
                for (String key : vars.keySet()) {
                    values.putSingle(key, extractedValues.get(key));
                    indices.putSingle(key, startIndex);
                }
            }

            public void expand(MultivaluedMap values,
                               boolean encode,
                               StringBuilder builder) {
                String delim = ""; //$NON-NLS-1$
                for (String var : vars.keySet()) {
                    java.util.List varValues = values.get(var);
                    String value = ""; //$NON-NLS-1$
                    if (varValues != null) {
                        if (varValues.size() > 1) {
                            throw new IllegalArgumentException(Messages
                                .getMessage("variableContainsMoreThanOneValueForJoinOperator", var)); //$NON-NLS-1$
                        }
                        if (varValues.size() == 1) {
                            value = varValues.get(0);
                            if (value == null) {
                                continue;
                            }
                            builder.append(delim);
                            builder.append(var);
                            builder.append("="); //$NON-NLS-1$
                            if (encode) {
                                value = UriEncoder.encodeString(value);
                            }
                            builder.append(value);
                            delim = arg;
                        }
                    }
                }
            }
        }
    }

    /**
     * This compilation handler is used for creating an expansion string by
     * replacing all variables with their values from the provided map.
     */
    private static class BitWorkingTemplateExpander extends AbstractTemplateExpander implements
        BitWorkingCompilationHandler {

        public BitWorkingTemplateExpander(MultivaluedMap values, StringBuilder out) {
            super(values, out);
        }

        public void variable(String name, String defaultValue) {
            if (values == null) {
                throw new NullPointerException(Messages.getMessage("variableIsNull", name)); //$NON-NLS-1$
            }
            Variable expander = new Variable(name, null, defaultValue);
            expander.expand(values, false, out);
        }

        public void operator(String name, String arg, Map vars) {
            if (values == null) {
                throw new NullPointerException(Messages.getMessage("variableIsNull", name)); //$NON-NLS-1$
            }
            BitWorkingOperator expander = BitWorkingOperator.forName(name);
            if (expander == null) {
                throw new IllegalArgumentException(Messages.getMessage("unsupportedOperator", name)); //$NON-NLS-1$
            }
            expander.expand(values, false, out);
        }

    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy