com.ocpsoft.pretty.faces.url.URLPatternParser Maven / Gradle / Ivy
/*
* Copyright 2010 Lincoln Baxter, III
*
* Licensed 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 com.ocpsoft.pretty.faces.url;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.ocpsoft.pretty.PrettyException;
import com.ocpsoft.pretty.faces.config.mapping.PathParameter;
import com.ocpsoft.pretty.faces.el.ExpressionProcessorRunner;
import com.ocpsoft.pretty.faces.el.Expressions;
/**
* @author Lincoln Baxter, III
*/
public class URLPatternParser
{
private static final Pattern EL_REGEX_PATTERN = Pattern.compile(Expressions.EL_REGEX);
private final String originalPattern;
private boolean elPattern;
private URL urlPattern = null;
private Pattern urlElPattern;
private List pathSegments = new ArrayList();
private List pathParameters = new ArrayList();
/**
* Set the pattern for which this parser will match. Find and replace all el expressions with regular expressions to
* extract values from parsed URLs. Also extract all parameter names from expressions, and replace with valid EL
*
* @param pattern Pattern to use as a parse template
*/
public URLPatternParser(final String pattern)
{
originalPattern = pattern;
Matcher expressionMatcher = EL_REGEX_PATTERN.matcher(pattern);
StringBuffer segmentableExpressions = new StringBuffer();
elPattern = false;
if (pattern.indexOf('|') != -1) {
elPattern = true;
}
/*
* Extract path parameters
*/
int paramIndex = 0;
while (expressionMatcher.find())
{
elPattern = true;
String expression = expressionMatcher.group(1);
PathParameter param = ExpressionProcessorRunner.process(expression);
param.setPosition(paramIndex);
pathParameters.add(param);
expressionMatcher.appendReplacement(segmentableExpressions, Segment.parameterize(paramIndex));
paramIndex++;
}
expressionMatcher.appendTail(segmentableExpressions);
this.pathParameters = Collections.unmodifiableList(pathParameters);
URL segmentedPattern = new URL(segmentableExpressions.toString());
/*
* Extract path segments, overlaying regexes found during parameter
* discovery.
*/
for (String segmentPattern : segmentedPattern.getSegments())
{
Segment segment = new Segment();
segment.setTemplate(segmentPattern);
StringBuffer regex = new StringBuffer();
Matcher parameterMatcher = Segment.getTemplateMatcher(segmentPattern);
while (parameterMatcher.find())
{
String group = parameterMatcher.group(1);
PathParameter parameter = pathParameters.get(Integer.valueOf(group));
segment.addParameter(parameter);
parameterMatcher.appendReplacement(regex, "(" + parameter.getRegex() + ")");
}
parameterMatcher.appendTail(regex);
segment.setRegex(regex.toString());
pathSegments.add(segment);
}
List regexSegments = new ArrayList();
for (Segment s : pathSegments)
{
regexSegments.add(s.getRegex());
}
urlPattern = new URL(regexSegments, segmentedPattern.getMetadata().copy());
if (elPattern) {
urlElPattern = Pattern.compile(urlPattern.toURL());
}
this.pathSegments = Collections.unmodifiableList(pathSegments);
}
/**
* Return true if this parser matches the given URL, otherwise, return false.
*/
public boolean matches(final URL target)
{
if (elPattern) {
return urlElPattern.matcher(target.toURL()).matches();
}
else {
return urlPattern.toURL().equals(target.toURL());
}
}
/**
* Return true if this parser matches the given URL, otherwise, return false.
*/
@Deprecated
public boolean matches(final String target)
{
URL url = new URL(target);
return matches(url);
}
/**
* Return the list of parameters specified by this pattern.
*/
public List getPathParameters()
{
return pathParameters;
}
/**
* Builds a list of PathParameters for this UrlPattern, extracted from the provided URL (assuming a match is found).
* This list excludes duplicate named parameters.
*/
public List parse(final URL url)
{
List result = new ArrayList();
String inboundUrl = url.toURL();
if (this.matches(url))
{
Iterator iter = pathSegments.iterator();
while (iter.hasNext())
{
Segment segment = iter.next();
String regex = "";
if (url.hasLeadingSlash() && !"/".equals(url.toURL()))
{
regex += "/";
}
regex += segment.getRegex();
if (iter.hasNext() || url.hasTrailingSlash())
{
regex += "/";
}
Matcher segmentMatcher = Pattern.compile(regex).matcher(inboundUrl);
if (segmentMatcher.find())
{
for (int j = 0; j < segment.numParameters(); j++)
{
String value = segmentMatcher.group(j + 1);
PathParameter param = segment.getParameter(j).copy();
param.setValue(value);
result.add(param);
}
int regionEnd = segmentMatcher.end();
inboundUrl = inboundUrl.substring(regionEnd - 1);
}
else
{
throw new PrettyException("Error parsing url: <" + url
+ ">, a parameter did not match compiled segment in pattern: " + originalPattern);
}
}
}
else
{
throw new IllegalArgumentException("The given URL: " + url + ", cannot be parsed by the pattern: "
+ originalPattern);
}
return result;
}
/**
* URL encoding/decoding is not a concern of this method.
*
* @param params Array of Object parameters, in order, to be substituted for mapping pattern values or el
* expressions. This method will call the toString() method on each object provided.
*
* If only one param is specified and it is an instance of List, the list items will be used as parameters
* instead. An empty list or a single null parameter are both treated as if no parameters were specified.
*
* E.g: getMappedUrl(12,55,"foo","bar") for a pattern of /#{el.one}/#{el.two}/#{el.three}/#{el.four}/ will
* return the String: "/12/55/foo/bar/"
* @return A URL based on this object's urlPatten, with values substituted for el expressions in the order provided
*/
public URL getMappedURL(final Object... params)
{
URL result = null;
if (params != null)
{
Object[] parameters = params;
/*
* Check to see if our parameters were provided as a single List
*/
if ((params.length == 1) && (params[0] != null) && (params[0] instanceof List>))
{
List> list = (List>) params[0];
if (list.size() == 0)
{
parameters = new Object[0];
}
else
{
parameters = list.toArray(params);
}
}
else if ((params.length == 1) && (params[0] == null))
{
parameters = new Object[0];
}
/*
* Assert that we have the proper number of parameters.
*/
if (getParameterCount() != parameters.length)
{
throw new PrettyException("Invalid number of path parameters supplied for pattern: " + originalPattern
+ ", expected <" + getParameterCount() + ">, but got <" + parameters.length + ">");
}
/*
* Build the result URL
*/
int paramIndex = 0;
List resultSegments = new ArrayList();
for (Segment segment : pathSegments)
{
String template = segment.getTemplate();
Matcher parameterMatcher = Segment.getTemplateMatcher(template);
StringBuffer sb = new StringBuffer();
while (parameterMatcher.find())
{
/*
* We need to escape $ and \ because they have a special meaning when
* used in Matcher#appendReplacement(). From the docs:
*
* Note that backslashes (\) and dollar signs ($) in the replacement string
* may cause the results to be different than if it were being treated as a
* literal replacement string. Dollar signs may be treated as references to
* captured subsequences as described above, and backslashes are used to
* escape literal characters in the replacement string.
*/
String replacement = parameters[paramIndex].toString()
.replace("$", "\\$")
.replace("\\", "\\\\");
parameterMatcher.appendReplacement(sb, replacement);
paramIndex++;
}
parameterMatcher.appendTail(sb);
resultSegments.add(sb.toString());
}
result = new URL(resultSegments, urlPattern.getMetadata());
}
else if (getParameterCount() > 0)
{
throw new PrettyException("Invalid number of parameters supplied: " + originalPattern + ", expected <"
+ getParameterCount() + ">, got <0>");
}
return result;
}
/**
* Get the number of URL parameters that this parser expects to find in any given input string
*
* @return Number of parameters
*/
public int getParameterCount()
{
return pathParameters.size();
}
public Object getPattern()
{
return originalPattern;
}
/**
* @return whether the URL pattern is an expression language
*/
public boolean isElPattern()
{
return elPattern;
}
}