Index: src/main/java/org/tuckey/web/filters/urlrewrite/substitution/FunctionReplacer.java =================================================================== --- src/main/java/org/tuckey/web/filters/urlrewrite/substitution/FunctionReplacer.java (revision 442) +++ src/main/java/org/tuckey/web/filters/urlrewrite/substitution/FunctionReplacer.java (working copy) @@ -34,7 +34,9 @@ */ package org.tuckey.web.filters.urlrewrite.substitution; -import org.tuckey.web.filters.urlrewrite.functions.StringFunctions; +import org.tuckey.web.filters.urlrewrite.substitution.interpreter.Context; +import org.tuckey.web.filters.urlrewrite.substitution.interpreter.ParseException; +import org.tuckey.web.filters.urlrewrite.substitution.interpreter.ToValueNode; import org.tuckey.web.filters.urlrewrite.utils.Log; import java.util.Collections; @@ -63,96 +65,16 @@ } - public String substitute(String subjectOfReplacement, SubstitutionContext ctx, - SubstitutionFilterChain nextFilter) { - Matcher functionMatcher = functionPattern.matcher(subjectOfReplacement); - StringBuffer sb = new StringBuffer(); - boolean anyMatches = false; - - int lastAppendPosition = 0; - while (functionMatcher.find()) { - anyMatches = true; - int groupCount = functionMatcher.groupCount(); - if (groupCount < 1) { - log.error("group count on function finder regex is not as expected"); - if (log.isDebugEnabled()) { - log.error("functionMatcher: " + functionMatcher.toString()); - } - continue; - } - String varStr = functionMatcher.group(1); - String varValue = ""; - if (varStr != null) { - varValue = functionReplace(varStr, ctx, nextFilter); - if (log.isDebugEnabled()) log.debug("resolved to: " + varValue); - } else { - if (log.isDebugEnabled()) log.debug("variable reference is null " + functionMatcher); - } - String stringBeforeMatch = subjectOfReplacement.substring(lastAppendPosition, functionMatcher.start()); - sb.append(nextFilter.substitute(stringBeforeMatch, ctx)); - sb.append(varValue); - lastAppendPosition = functionMatcher.end(); + public String substitute(String subjectOfReplacement, SubstitutionContext ctx, SubstitutionFilterChain nextFilter) { + Context context = new Context(subjectOfReplacement); + ToValueNode node = new ToValueNode(ctx, nextFilter); + try { + node.parse(context); + return node.evaluate(); + } catch (ParseException e) { + log.error(e.getMessage(), e); } - if (anyMatches) { - String stringAfterMatch = subjectOfReplacement.substring(lastAppendPosition); - sb.append(nextFilter.substitute(stringAfterMatch, ctx)); - log.debug("replaced sb is " + sb); - return sb.toString(); - } - return nextFilter.substitute(subjectOfReplacement, ctx); + return subjectOfReplacement; } - - /** - * Handles the fetching of the variable value from the request. - */ - private String functionReplace(String originalVarStr, SubstitutionContext ctx, final SubstitutionFilterChain nextFilter) { - // get the sub name if any ie for headers etc header:user-agent - String varSubName = null; - String varType; - int colonIdx = originalVarStr.indexOf(":"); - if (colonIdx != -1 && colonIdx + 1 < originalVarStr.length()) { - varSubName = originalVarStr.substring(colonIdx + 1); - varType = originalVarStr.substring(0, colonIdx); - if (log.isDebugEnabled()) log.debug("function ${" + originalVarStr + "} type: " + varType + - ", name: '" + varSubName + "'"); - } else { - varType = originalVarStr; - if (log.isDebugEnabled()) log.debug("function ${" + originalVarStr + "} type: " + varType); - } - String functionResult = ""; - SubstitutionFilterChain redoFunctionFilter = new SubstitutionFilterChain() { - public String substitute(String string, SubstitutionContext ctx) { - return FunctionReplacer.this.substitute(string, ctx, nextFilter); - } - - }; - // check for some built in functions - if ("replace".equalsIgnoreCase(varType) || "replaceAll".equalsIgnoreCase(varType)) { - functionResult = StringFunctions.replaceAll(varSubName, redoFunctionFilter, ctx); - } else if ("replaceFirst".equalsIgnoreCase(varType)) { - functionResult = StringFunctions.replaceFirst(varSubName, redoFunctionFilter, ctx); - } else if ("escape".equalsIgnoreCase(varType)) { - functionResult = StringFunctions.escape(varSubName, redoFunctionFilter, ctx); - } else if ("escapePath".equalsIgnoreCase(varType)) { - functionResult = StringFunctions.escapePath(varSubName, redoFunctionFilter, ctx); - } else if ("unescape".equalsIgnoreCase(varType)) { - functionResult = StringFunctions.unescape(varSubName, redoFunctionFilter, ctx); - } else if ("unescapePath".equalsIgnoreCase(varType)) { - functionResult = StringFunctions.unescapePath(varSubName, redoFunctionFilter, ctx); - } else if ("lower".equalsIgnoreCase(varType) || "toLower".equalsIgnoreCase(varType)) { - functionResult = StringFunctions.toLower(varSubName, redoFunctionFilter, ctx); - } else if ("upper".equalsIgnoreCase(varType) || "toUpper".equalsIgnoreCase(varType)) { - functionResult = StringFunctions.toUpper(varSubName, redoFunctionFilter, ctx); - } else if ("trim".equalsIgnoreCase(varType)) { - functionResult = StringFunctions.trim(varSubName, redoFunctionFilter, ctx); - } else if ("length".equalsIgnoreCase(varType)) { - functionResult = StringFunctions.length(varSubName, redoFunctionFilter, ctx); - } else { - log.error("function ${" + originalVarStr + "} type '" + varType + "' not a valid type"); - } - return functionResult; - } - - } Index: src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/Context.java =================================================================== --- src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/Context.java (revision 0) +++ src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/Context.java (working copy) @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2013, Kazuro Fukuhara All rights reserved. + * Licensed under the BSD License. see http://opensource.org/licenses/BSD-3-Clause + */ + +package org.tuckey.web.filters.urlrewrite.substitution.interpreter; + +/** + * @author Kazuro Fukuhara + */ +public class Context { + + private Tokenizer tokenizer; + private String currentToken; + + public Context(String text) { + tokenizer = new Tokenizer(text); + nextToken(); + } + + public String nextToken() { + if (tokenizer.hasNext()) { + currentToken = tokenizer.next(); + } else { + currentToken = null; + } + return currentToken; + } + + public String currentToken() { + return currentToken; + } + + public void skipToken(String token) throws ParseException { + if (!token.equals(currentToken)) { + throw new ParseException("Warning: " + token + " is expected, but " + currentToken + " is found."); + } + nextToken(); + } +} Property changes on: src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/Context.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Id Author Date Revision HeadURL \ No newline at end of property Index: src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/ElementNode.java =================================================================== --- src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/ElementNode.java (revision 0) +++ src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/ElementNode.java (working copy) @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2013, Kazuro Fukuhara All rights reserved. + * Licensed under the BSD License. see http://opensource.org/licenses/BSD-3-Clause + */ + +package org.tuckey.web.filters.urlrewrite.substitution.interpreter; + +import org.tuckey.web.filters.urlrewrite.substitution.SubstitutionContext; +import org.tuckey.web.filters.urlrewrite.substitution.SubstitutionFilterChain; + +/** + * <element> ::= <value> | <function> + * + * @author Kazuro Fukuhara + */ +public class ElementNode implements Node { + Node node; + SubstitutionContext ctx; + SubstitutionFilterChain nextFilter; + + ElementNode(SubstitutionContext ctx, SubstitutionFilterChain nextFilter) { + this.ctx = ctx; + this.nextFilter = nextFilter; + } + + public void parse(Context context) throws ParseException { + if (context.currentToken().startsWith(Tokenizer.FUNC_START)) { + node = new FunctionNode(ctx, nextFilter); + node.parse(context); + } else { + node = new ValueNode(ctx, nextFilter); + node.parse(context); + } + } + + @Override + public String toString() { + return node.toString(); + } + + public String evaluate() { + return node.evaluate(); + } + +} Property changes on: src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/ElementNode.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Id Author Date Revision HeadURL \ No newline at end of property Index: src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/Function.java =================================================================== --- src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/Function.java (revision 0) +++ src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/Function.java (working copy) @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2013, Kazuro Fukuhara All rights reserved. + * Licensed under the BSD License. see http://opensource.org/licenses/BSD-3-Clause + */ + +package org.tuckey.web.filters.urlrewrite.substitution.interpreter; + +import org.tuckey.web.filters.urlrewrite.functions.StringFunctions; +import org.tuckey.web.filters.urlrewrite.substitution.SubstitutionContext; +import org.tuckey.web.filters.urlrewrite.substitution.SubstitutionFilterChain; + +/** + * + * @author Kazuro Fukuhara + */ +public enum Function { + + REPLACE { + @Override + String execute(String arg, SubstitutionContext ctx, SubstitutionFilterChain nextFilter) { + return REPLACEALL.execute(arg, ctx, nextFilter); + } + }, + + REPLACEALL { + @Override + String execute(String args, SubstitutionContext ctx, SubstitutionFilterChain nextFilter) { + return StringFunctions.replaceAll(args, nextFilter, ctx); + } + }, + + REPLACEFIRST { + @Override + String execute(String arg, SubstitutionContext ctx, SubstitutionFilterChain nextFilter) { + return StringFunctions.replaceFirst(arg, nextFilter, ctx); + + } + }, + ESCAPE { + @Override + String execute(String arg, SubstitutionContext ctx, SubstitutionFilterChain nextFilter) { + return StringFunctions.escape(arg, nextFilter, ctx); + + } + }, + ESCAPEPATH { + @Override + String execute(String arg, SubstitutionContext ctx, SubstitutionFilterChain nextFilter) { + return StringFunctions.escapePath(arg, nextFilter, ctx); + } + }, + + UNESCAPE { + @Override + String execute(String arg, SubstitutionContext ctx, SubstitutionFilterChain nextFilter) { + return StringFunctions.unescape(arg, nextFilter, ctx); + } + }, + UNESCAPEPATH { + @Override + String execute(String arg, SubstitutionContext ctx, SubstitutionFilterChain nextFilter) { + return StringFunctions.unescapePath(arg, nextFilter, ctx); + } + }, + LOWER { + @Override + String execute(String arg, SubstitutionContext ctx, SubstitutionFilterChain nextFilter) { + return TOLOWER.execute(arg, ctx, nextFilter); + } + }, + UPPER { + @Override + String execute(String arg, SubstitutionContext ctx, SubstitutionFilterChain nextFilter) { + return TOUPPER.execute(arg, ctx, nextFilter); + } + }, + TOLOWER { + @Override + String execute(String arg, SubstitutionContext ctx, SubstitutionFilterChain nextFilter) { + return StringFunctions.toLower(arg, nextFilter, ctx); + + } + }, + TOUPPER { + @Override + String execute(String arg, SubstitutionContext ctx, SubstitutionFilterChain nextFilter) { + return StringFunctions.toUpper(arg, nextFilter, ctx); + } + }, + TRIM { + @Override + String execute(String arg, SubstitutionContext ctx, SubstitutionFilterChain nextFilter) { + return StringFunctions.trim(arg, nextFilter, ctx); + } + }, + + LENGTH { + @Override + String execute(String arg, SubstitutionContext ctx, SubstitutionFilterChain nextFilter) { + return StringFunctions.length(arg, nextFilter, ctx); + } + }; + + abstract String execute(String arg, SubstitutionContext ctx, SubstitutionFilterChain nextFilter); +} Property changes on: src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/Function.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Id Author Date Revision HeadURL \ No newline at end of property Index: src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/FunctionContentNode.java =================================================================== --- src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/FunctionContentNode.java (revision 0) +++ src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/FunctionContentNode.java (working copy) @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2013, Kazuro Fukuhara All rights reserved. + * Licensed under the BSD License. see http://opensource.org/licenses/BSD-3-Clause + */ + +package org.tuckey.web.filters.urlrewrite.substitution.interpreter; + +import org.tuckey.web.filters.urlrewrite.substitution.SubstitutionContext; +import org.tuckey.web.filters.urlrewrite.substitution.SubstitutionFilterChain; + +/** + * <functioncontent> ::= <functionName> : <param> + * + * @author Kazuro Fukuhara + */ +public class FunctionContentNode implements Node { + private FunctionNameNode funcNameNode; + private ParamNode paramNode; + + SubstitutionContext ctx; + SubstitutionFilterChain nextFilter; + + FunctionContentNode(SubstitutionContext ctx, SubstitutionFilterChain nextFilter) { + this.ctx = ctx; + this.nextFilter = nextFilter; + } + + public void parse(Context context) throws ParseException { + funcNameNode = new FunctionNameNode(); + funcNameNode.parse(context); + context.skipToken(Tokenizer.PARAM_DELIMITER); + paramNode = new ParamNode(ctx, nextFilter); + paramNode.parse(context); + } + + public String evaluate() { + Function func = funcNameNode.getFunction(); + String param = paramNode.evaluate(); + return func.execute(param, ctx, nextFilter); + } + + @Override + public String toString() { + return funcNameNode + Tokenizer.PARAM_DELIMITER + paramNode; + } +} Property changes on: src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/FunctionContentNode.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Id Author Date Revision HeadURL \ No newline at end of property Index: src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/FunctionNameNode.java =================================================================== --- src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/FunctionNameNode.java (revision 0) +++ src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/FunctionNameNode.java (working copy) @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2013, Kazuro Fukuhara All rights reserved. + * Licensed under the BSD License. see http://opensource.org/licenses/BSD-3-Clause + */ + +package org.tuckey.web.filters.urlrewrite.substitution.interpreter; + +/** + * + * <functionName> ::= replace | replaceAll | replaceFirst | escape | + * escapePath | lower | upper | toLower | toUpper | trim | length + * + * @author Kazuro Fukuhara + */ +public class FunctionNameNode implements Node { + private Function function; + + public void parse(Context context) throws ParseException { + String name = context.currentToken(); + context.skipToken(name); + try { + function = Function.valueOf(name.toUpperCase()); + } catch (IllegalArgumentException e) { + throw new ParseException(e.getMessage()); + } catch (NullPointerException e) { + throw new ParseException(e.getMessage()); + } + } + + @Override + public String toString() { + return function.toString().toLowerCase(); + } + + public Function getFunction() { + return function; + } + + /** + * return function name. but this is no mean because {@link #getFunction()} + * return this as {@link Function} + */ + public String evaluate() { + return function.toString(); + } +} Property changes on: src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/FunctionNameNode.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Id Author Date Revision HeadURL \ No newline at end of property Index: src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/FunctionNode.java =================================================================== --- src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/FunctionNode.java (revision 0) +++ src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/FunctionNode.java (working copy) @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2013, Kazuro Fukuhara All rights reserved. + * Licensed under the BSD License. see http://opensource.org/licenses/BSD-3-Clause + */ + +package org.tuckey.web.filters.urlrewrite.substitution.interpreter; + +import org.tuckey.web.filters.urlrewrite.substitution.SubstitutionContext; +import org.tuckey.web.filters.urlrewrite.substitution.SubstitutionFilterChain; + +/** + * <function> ::= ${ <functionContent> } + * + * + * @author Kazuro Fukuhara + */ +public class FunctionNode implements Node { + + FunctionContentNode content; + SubstitutionContext ctx; + SubstitutionFilterChain nextFilter; + + FunctionNode(SubstitutionContext ctx, SubstitutionFilterChain nextFilter) { + this.ctx = ctx; + this.nextFilter = nextFilter; + } + + public void parse(Context context) throws ParseException { + context.skipToken(Tokenizer.FUNC_START); + content = new FunctionContentNode(ctx, nextFilter); + content.parse(context); + context.skipToken(Tokenizer.FUNC_END); + } + + @Override + public String toString() { + return Tokenizer.FUNC_START + content + Tokenizer.FUNC_END; + } + + public String evaluate() { + return content.evaluate(); + } +} Property changes on: src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/FunctionNode.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Id Author Date Revision HeadURL \ No newline at end of property Index: src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/Node.java =================================================================== --- src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/Node.java (revision 0) +++ src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/Node.java (working copy) @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2013, Kazuro Fukuhara All rights reserved. + * Licensed under the BSD License. see http://opensource.org/licenses/BSD-3-Clause + */ + +package org.tuckey.web.filters.urlrewrite.substitution.interpreter; + +/** + * Abstraction of each node in parse tree. + * + * @author Kazuro Fukuhara + */ +public interface Node { + void parse(Context context) throws ParseException; + + /** + * process this node. return value will differ for each concrete class. + * + */ + String evaluate(); +} Property changes on: src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/Node.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Id Author Date Revision HeadURL \ No newline at end of property Index: src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/ParamNode.java =================================================================== --- src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/ParamNode.java (revision 0) +++ src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/ParamNode.java (working copy) @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2013, Kazuro Fukuhara All rights reserved. + * Licensed under the BSD License. see http://opensource.org/licenses/BSD-3-Clause + */ + +package org.tuckey.web.filters.urlrewrite.substitution.interpreter; + +import org.tuckey.web.filters.urlrewrite.substitution.SubstitutionContext; +import org.tuckey.web.filters.urlrewrite.substitution.SubstitutionFilterChain; + +import java.util.ArrayList; +import java.util.List; + +/** + * <param> ::= <element> * | <element> * : <param> + * + * @author Kazuro Fukuhara + */ +public class ParamNode implements Node { + List elementList; + ParamNode secondElement; + + SubstitutionContext ctx; + SubstitutionFilterChain nextFilter; + + ParamNode(SubstitutionContext ctx, SubstitutionFilterChain nextFilter) { + this.ctx = ctx; + this.nextFilter = nextFilter; + this.elementList = new ArrayList(); + } + + public void parse(Context context) throws ParseException { + while (true) { + if (context.currentToken() != null) { + if (context.currentToken().equals(Tokenizer.FUNC_START)) { + ElementNode en = new ElementNode(ctx, nextFilter); + en.parse(context); + elementList.add(en); + } else if (context.currentToken().equals(Tokenizer.PARAM_DELIMITER)) { + context.skipToken(Tokenizer.PARAM_DELIMITER); + secondElement = new ParamNode(ctx, nextFilter); + secondElement.parse(context); + } else if (context.currentToken().equals(Tokenizer.FUNC_END)) { + break; + } else { + ElementNode en = new ElementNode(ctx, nextFilter); + en.parse(context); + elementList.add(en); + } + } else { + break; + } + } + } + + public String evaluate() { + StringBuilder sb = new StringBuilder(); + for (ElementNode en : elementList) { + sb.append(en.evaluate()); + } + if (secondElement != null) { + sb.append(Tokenizer.PARAM_DELIMITER); + sb.append(secondElement.evaluate()); + } + return sb.toString(); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (ElementNode en : elementList) { + sb.append(en.toString()); + } + if (secondElement != null) { + sb.append(Tokenizer.PARAM_DELIMITER); + sb.append(secondElement.toString()); + } + return sb.toString(); + } +} Property changes on: src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/ParamNode.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Id Author Date Revision HeadURL \ No newline at end of property Index: src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/ParseException.java =================================================================== --- src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/ParseException.java (revision 0) +++ src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/ParseException.java (working copy) @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2013, Kazuro Fukuhara All rights reserved. + * Licensed under the BSD License. see http://opensource.org/licenses/BSD-3-Clause + */ + +package org.tuckey.web.filters.urlrewrite.substitution.interpreter; + +/** + * represents failure of parse in "to" value + * + * @author Kazuro Fukuhara + */ +public final class ParseException extends Exception { + private static final long serialVersionUID = 1L; + + public ParseException(String arg0) { + super(arg0); + } + +} Property changes on: src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/ParseException.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Id Author Date Revision HeadURL \ No newline at end of property Index: src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/ToValueNode.java =================================================================== --- src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/ToValueNode.java (revision 0) +++ src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/ToValueNode.java (working copy) @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2013, Kazuro Fukuhara All rights reserved. + * Licensed under the BSD License. see http://opensource.org/licenses/BSD-3-Clause + */ + +package org.tuckey.web.filters.urlrewrite.substitution.interpreter; + +import org.tuckey.web.filters.urlrewrite.substitution.SubstitutionContext; +import org.tuckey.web.filters.urlrewrite.substitution.SubstitutionFilterChain; + +import java.util.ArrayList; +import java.util.List; + +/** + * + * <tovalue> ::= <element> * + * + * @author Kazuro Fukuhara + */ +public class ToValueNode implements Node { + + List elementList = new ArrayList(); + + SubstitutionContext ctx; + SubstitutionFilterChain nextFilter; + + public ToValueNode(SubstitutionContext ctx, SubstitutionFilterChain nextFilter) { + this.ctx = ctx; + this.nextFilter = nextFilter; + } + + public void parse(Context context) throws ParseException { + while (true) { + if (context.currentToken() != null) { + Node oneNode = new ElementNode(ctx, nextFilter); + oneNode.parse(context); + elementList.add(oneNode); + } else { + break; + } + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (Node content : elementList) { + sb.append(content.toString()); + } + return sb.toString(); + } + + public String evaluate() { + StringBuilder sb = new StringBuilder(); + for (Node content : elementList) { + sb.append(content.evaluate()); + } + return sb.toString(); + } + +} Property changes on: src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/ToValueNode.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Id Author Date Revision HeadURL \ No newline at end of property Index: src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/Tokenizer.java =================================================================== --- src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/Tokenizer.java (revision 0) +++ src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/Tokenizer.java (working copy) @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2013, Kazuro Fukuhara All rights reserved. + * Licensed under the BSD License. see http://opensource.org/licenses/BSD-3-Clause + */ + +package org.tuckey.web.filters.urlrewrite.substitution.interpreter; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Tokenize "to" value. + * + * @author Kazuro Fukuhara + */ +public class Tokenizer { + static final String FUNC_START = "${"; + static final String FUNC_END = "}"; + static final String PARAM_DELIMITER = ":"; + + private static final String[] TOKEN_DELIITER_ARY = { FUNC_START, FUNC_END, PARAM_DELIMITER }; + + List tokens = new ArrayList(); + int pos = 0; + + Tokenizer(String src) { + int currentPos = 0; + int startPos = 0; + int endPos = 0; + int paramPos = 0; + + while (currentPos < src.length()) { + + startPos = src.indexOf(FUNC_START, currentPos); + endPos = src.indexOf(FUNC_END, currentPos); + paramPos = src.indexOf(PARAM_DELIMITER, currentPos); + + Set posSet = new HashSet(); + + posSet.add(startPos); + posSet.add(endPos); + posSet.add(paramPos); + + posSet.remove(-1); + if (posSet.isEmpty()) { + tokens.add(src.substring(currentPos)); + break; + } + + List posList = new ArrayList(posSet); + int candidatePos = Collections.min(posList); + if (candidatePos != 0 && currentPos != candidatePos) { + String newToken = src.substring(currentPos, candidatePos); + if (src.charAt(candidatePos) == FUNC_END.charAt(0) && newToken.contains("%{")) { + newToken = newToken + FUNC_END; + candidatePos++; + } + tokens.add(newToken); + currentPos += newToken.length(); + } + if (candidatePos != src.length()) { + for (String tokenDelimiter : TOKEN_DELIITER_ARY) { + if (src.charAt(candidatePos) == tokenDelimiter.charAt(0)) { + tokens.add(tokenDelimiter); + currentPos += tokenDelimiter.length(); + } + } + } + } + } + + public boolean hasNext() { + return pos != tokens.size(); + } + + public String next() { + String candidate = tokens.get(pos); + pos++; + return candidate; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Tokenizer [tokens="); + builder.append(tokens); + builder.append(", pos="); + builder.append(pos); + builder.append("]"); + return builder.toString(); + } + +} Property changes on: src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/Tokenizer.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Id Author Date Revision HeadURL \ No newline at end of property Index: src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/ValueNode.java =================================================================== --- src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/ValueNode.java (revision 0) +++ src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/ValueNode.java (working copy) @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2013, Kazuro Fukuhara All rights reserved. + * Licensed under the BSD License. see http://opensource.org/licenses/BSD-3-Clause + */ + +package org.tuckey.web.filters.urlrewrite.substitution.interpreter; + +import org.tuckey.web.filters.urlrewrite.substitution.SubstitutionContext; +import org.tuckey.web.filters.urlrewrite.substitution.SubstitutionFilterChain; + +/** + * <value> ::= any string + * + * @author Kazuro Fukuhara + */ +public class ValueNode implements Node { + String value; + + SubstitutionContext ctx; + SubstitutionFilterChain nextFilter; + + ValueNode(SubstitutionContext ctx, SubstitutionFilterChain nextFilter) { + this.ctx = ctx; + this.nextFilter = nextFilter; + } + + public void parse(Context context) throws ParseException { + value = context.currentToken(); + context.skipToken(value); + if (value.equals(Tokenizer.FUNC_START)) { + throw new ParseException(value + " starts ${"); + } + } + + @Override + public String toString() { + return value; + } + + public String evaluate() { + return nextFilter.substitute(value, ctx); + } + +} Property changes on: src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/ValueNode.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Id Author Date Revision HeadURL \ No newline at end of property Index: src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/package-info.java =================================================================== --- src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/package-info.java (revision 0) +++ src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/package-info.java (working copy) @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2013, Kazuro Fukuhara All rights reserved. + * Licensed under the BSD License. see http://opensource.org/licenses/BSD-3-Clause + */ + +/** + * This package processes function in "to" value with + * Interpreter pattern . + * BNF for Interpreter pattern is below. + *
    + *
  • <tovalue> ::= <element> *
  • + *
  • <element> ::= <value> | <function>
  • + *
  • <value> ::= any string
  • + *
  • <function> ::= ${ <functionContent> }
  • + *
  • <functioncontent> ::= <functionName> : <param>
  • + *
  • <param> ::= <element> * | <element> * : <param>
  • + *
  • <functionName> ::= replace | replaceAll | replaceFirst | escape | escapePath | lower | upper | toLower | toUpper | trim | length
  • + *
+ * + * + * + * @author Kazuro Fukuhara + */ +package org.tuckey.web.filters.urlrewrite.substitution.interpreter; + Property changes on: src/main/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/package-info.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Id Author Date Revision HeadURL \ No newline at end of property Index: src/test/java/org/tuckey/web/filters/urlrewrite/RuleTest.java =================================================================== --- src/test/java/org/tuckey/web/filters/urlrewrite/RuleTest.java (revision 442) +++ src/test/java/org/tuckey/web/filters/urlrewrite/RuleTest.java (working copy) @@ -843,4 +843,36 @@ NormalRewrittenUrl rewrittenUrl = (NormalRewrittenUrl) rule.matches(request.getRequestURI(), request, response); assertEquals("/en/robots.txt?param1=value1¶m2=value2", rewrittenUrl.getTarget()); } + public void testLowerFunction() throws InvocationTargetException, IOException, ServletException { + NormalRule rule = new NormalRule(); + rule.setFrom("^/lowerMe/([A-Z])+/$"); + rule.setTo("/lowerMe/${lower:$1}"); + rule.initialise(new MockServletContext()); + MockRequest request = new MockRequest("/lowerMe/A/"); + NormalRewrittenUrl rewrittenUrl = (NormalRewrittenUrl) rule.matches(request.getRequestURI(), request, response); + assertEquals("/lowerMe/a", rewrittenUrl.getTarget()); + } + + public void testIssue120() throws InvocationTargetException, IOException, ServletException { + NormalRule rule = new NormalRule(); + rule.setFrom("^/test/(.+)\\.html$"); + rule.setTo("/test/${upper:$1}/${upper:$1}.html"); + rule.initialise(new MockServletContext()); + MockRequest request = new MockRequest("/test/test.html"); + NormalRewrittenUrl rewrittenUrl = (NormalRewrittenUrl) rule.matches(request.getRequestURI(), request, response); + assertEquals("/test/TEST/TEST.html", rewrittenUrl.getTarget()); + } + + + public void testIssue120More() throws InvocationTargetException, IOException, ServletException { + NormalRule rule = new NormalRule(); + rule.setFrom("^/download/(abc+[0-9]{1}z[0-9]{1,9}i)/(.*)/(.*)$"); + rule.setTo("/file/download?workId=$1&type=${escape:$2}&name=${escape:$3}&originalUrl=%{request-url}"); + rule.initialise(new MockServletContext()); + MockRequest request = new MockRequest("/download/abc1z2i/wawawa/readme.txt"); + request.setRequestURL("dummytesturl"); + NormalRewrittenUrl rewrittenUrl = (NormalRewrittenUrl) rule.matches(request.getRequestURI(), request, response); + assertEquals("/file/download?workId=abc1z2i&type=wawawa&name=readme.txt&originalUrl=dummytesturl", + rewrittenUrl.getTarget()); + } } Index: src/test/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/ContextTest.java =================================================================== --- src/test/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/ContextTest.java (revision 0) +++ src/test/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/ContextTest.java (working copy) @@ -0,0 +1,35 @@ +package org.tuckey.web.filters.urlrewrite.substitution.interpreter; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +public class ContextTest { + + @Test + public void testContext() { + Context target = new Context("${escapePath:UTF-8:a b c : other / path}"); + assertThat(target.currentToken(), is("${")); + assertThat(target.nextToken(), is("escapePath")); + + assertThat(target.nextToken(), is(":")); + assertThat(target.nextToken(), is("UTF-8")); + assertThat(target.nextToken(), is(":")); + assertThat(target.nextToken(), is("a b c ")); + assertThat(target.nextToken(), is(":")); + assertThat(target.nextToken(), is(" other / path")); + assertThat(target.nextToken(), is("}")); + assertThat(target.nextToken(), nullValue()); + } + + @Test + public void testSkipToken() throws Exception { + Context target = new Context("${escapePath:UTF-8:a b c : other / path}"); + target.skipToken("${"); + assertThat(target.currentToken(), is("escapePath")); + assertThat(target.nextToken(), is(":")); + } + +} Property changes on: src/test/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/ContextTest.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Id Author Date Revision HeadURL \ No newline at end of property Index: src/test/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/FunctionNodeTest.java =================================================================== --- src/test/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/FunctionNodeTest.java (revision 0) +++ src/test/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/FunctionNodeTest.java (working copy) @@ -0,0 +1,20 @@ +package org.tuckey.web.filters.urlrewrite.substitution.interpreter; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.Test; +import org.tuckey.web.filters.urlrewrite.substitution.ChainedSubstitutionFilters; + +import java.util.Collections; + +public class FunctionNodeTest { + + @Test + public void test() throws ParseException { + Context context = new Context("${trim: abc ${lower:Hello World} }"); + FunctionNode node = new FunctionNode(null, new ChainedSubstitutionFilters(Collections.emptyList())); + node.parse(context); + assertThat(node.evaluate(), is("abc hello world")); + } +} Property changes on: src/test/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/FunctionNodeTest.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Id Author Date Revision HeadURL \ No newline at end of property Index: src/test/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/FunctionTest.java =================================================================== --- src/test/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/FunctionTest.java (revision 0) +++ src/test/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/FunctionTest.java (working copy) @@ -0,0 +1,97 @@ +package org.tuckey.web.filters.urlrewrite.substitution.interpreter; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.Test; +import org.tuckey.web.filters.urlrewrite.substitution.ChainedSubstitutionFilters; + +import java.io.UnsupportedEncodingException; +import java.util.Collections; + +/** + * @author Kazuro Fukuhara + */ +public class FunctionTest { + + @Test + public void testTolower() { + assertThat(Function.TOLOWER.execute("ABCD", null, new ChainedSubstitutionFilters(Collections.EMPTY_LIST)), is("abcd")); + assertThat(Function.TOLOWER.execute("abCD1234XYzw", null, new ChainedSubstitutionFilters(Collections.EMPTY_LIST)), + is("abcd1234xyzw")); + } + + @Test + public void testToupper() { + assertThat(Function.TOUPPER.execute("abcd", null, new ChainedSubstitutionFilters(Collections.EMPTY_LIST)), is("ABCD")); + assertThat(Function.TOUPPER.execute("abCD1234XYzw", null, new ChainedSubstitutionFilters(Collections.EMPTY_LIST)), + is("ABCD1234XYZW")); + } + + @Test + public void testTrim() { + assertThat(Function.TRIM.execute(" abcd ", null, new ChainedSubstitutionFilters(Collections.EMPTY_LIST)), is("abcd")); + assertThat(Function.TRIM.execute(" ", null, new ChainedSubstitutionFilters(Collections.EMPTY_LIST)), is("")); + } + + @Test + public void testReplaceall() { + assertThat(Function.REPLACEALL.execute("abcd1234abcxyzw:abc:stu", null, new ChainedSubstitutionFilters( + Collections.EMPTY_LIST)), is("stud1234stuxyzw")); + } + + @Test + public void testReplacefirst() { + assertThat(Function.REPLACEFIRST.execute("abcd1234abcxyzw:abc:stu", null, new ChainedSubstitutionFilters( + Collections.EMPTY_LIST)), is("stud1234abcxyzw")); + } + + @Test + public void testEscape() throws UnsupportedEncodingException { + // \u65E5\u672C\u8A9E is some Japanese string. + assertThat(Function.ESCAPE.execute("utf-8:abc", null, new ChainedSubstitutionFilters(Collections.EMPTY_LIST)), is("abc")); + assertThat( + Function.ESCAPE.execute("utf8:\u65E5\u672C\u8A9E", null, new ChainedSubstitutionFilters(Collections.EMPTY_LIST)), + is("%E6%97%A5%E6%9C%AC%E8%AA%9E")); + assertThat(Function.ESCAPE.execute("utf8:abcd!&'()0#$%&", null, new ChainedSubstitutionFilters(Collections.EMPTY_LIST)), + is("abcd%21%26%27%28%290%23%24%25%26")); + } + + @Test + public void testEscapepath() throws UnsupportedEncodingException { + /* + * I could't know this test is enough to keep the quality. Is there any + * specification or document for this method??? TODO check true spec of + * this method and do test properly + */ + assertThat(Function.ESCAPEPATH.execute("utf8:\u65E5\u672C\u8A9E", null, new ChainedSubstitutionFilters( + Collections.EMPTY_LIST)), is("%e6%97%a5%e6%9c%ac%e8%aa%9e")); + } + + @Test + public void testUnescape() throws UnsupportedEncodingException { + assertThat(Function.UNESCAPE.execute("utf-8:abc", null, new ChainedSubstitutionFilters(Collections.EMPTY_LIST)), + is("abc")); + assertThat(Function.UNESCAPE.execute("utf8:%E6%97%A5%E6%9C%AC%E8%AA%9E", null, new ChainedSubstitutionFilters( + Collections.EMPTY_LIST)), is("\u65E5\u672C\u8A9E")); + assertThat(Function.UNESCAPE.execute("utf8:abcd%21%26%27%28%290%23%24%25%26", null, new ChainedSubstitutionFilters( + Collections.EMPTY_LIST)), is("abcd!&'()0#$%&")); + } + + @Test + public void testUnescapepath() throws UnsupportedEncodingException { + /* + * I could't know this test is enough to keep the quality. Is there any + * specification or document for this method??? TODO check true spec of + * this method and do test properly + */ + assertThat(Function.UNESCAPEPATH.execute("utf8:\u65E5\u672C\u8A9E", null, new ChainedSubstitutionFilters( + Collections.EMPTY_LIST)), is("???")); + assertThat(Function.UNESCAPEPATH.execute("utf8:%e6%97%a5%e6%9c%ac%e8%aa%9e", null, new ChainedSubstitutionFilters( + Collections.EMPTY_LIST)), is("\u65E5\u672C\u8A9E")); + assertThat(Function.UNESCAPEPATH.execute("utf8:abcd%21%26%27%28%290%23%24%25%26", null, new ChainedSubstitutionFilters( + Collections.EMPTY_LIST)), is("abcd!&'()0#$%&")); + assertThat(Function.UNESCAPEPATH.execute("utf8:abCD%21%26%27%28%290%23%24%25%26", null, new ChainedSubstitutionFilters( + Collections.EMPTY_LIST)), is("abCD!&'()0#$%&")); + } +} Property changes on: src/test/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/FunctionTest.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Id Author Date Revision HeadURL \ No newline at end of property Index: src/test/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/ParamNodeTest.java =================================================================== --- src/test/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/ParamNodeTest.java (revision 0) +++ src/test/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/ParamNodeTest.java (working copy) @@ -0,0 +1,22 @@ +package org.tuckey.web.filters.urlrewrite.substitution.interpreter; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.Test; +import org.tuckey.web.filters.urlrewrite.substitution.ChainedSubstitutionFilters; + +import java.util.Collections; + +public class ParamNodeTest { + + @Test + public void test() throws ParseException { + Context context = new Context("abcd:hogehoge:wawawa"); + ParamNode node = new ParamNode(null, new ChainedSubstitutionFilters(Collections.EMPTY_LIST)); + node.parse(context); + assertThat(node.elementList.get(0).toString(), is("abcd")); + assertThat(node.secondElement.toString(), is("hogehoge:wawawa")); + } + +} Property changes on: src/test/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/ParamNodeTest.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Id Author Date Revision HeadURL \ No newline at end of property Index: src/test/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/ToValueNodeTest.java =================================================================== --- src/test/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/ToValueNodeTest.java (revision 0) +++ src/test/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/ToValueNodeTest.java (working copy) @@ -0,0 +1,39 @@ +package org.tuckey.web.filters.urlrewrite.substitution.interpreter; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.Assert; +import org.junit.Test; +import org.tuckey.web.filters.urlrewrite.substitution.ChainedSubstitutionFilters; + +import java.util.Collections; + +public class ToValueNodeTest { + + @Test + public void test() throws ParseException { + Context context = new Context("${replace:my cat is a blue cat:cat:dog}"); + ToValueNode node = new ToValueNode(null, new ChainedSubstitutionFilters(Collections.emptyList())); + node.parse(context); + ElementNode target = (ElementNode) node.elementList.get(0); + Assert.assertTrue(target.node instanceof FunctionNode); + assertThat(node.evaluate(), is("my dog is a blue dog")); + } + + @Test + public void test2() throws ParseException { + Context context = new Context("/search/${trim: abc ${lower:Hello World} }/hoge/"); + ToValueNode node = new ToValueNode(null, new ChainedSubstitutionFilters(Collections.EMPTY_LIST)); + node.parse(context); + assertThat(node.evaluate(), is("/search/abc hello world/hoge/")); + } + + @Test + public void test3() throws ParseException { + Context context = new Context("/search/${lower:${upper:ABCD}}"); + ToValueNode node = new ToValueNode(null, new ChainedSubstitutionFilters(Collections.EMPTY_LIST)); + node.parse(context); + assertThat(node.evaluate(), is("/search/abcd")); + } +} Property changes on: src/test/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/ToValueNodeTest.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Id Author Date Revision HeadURL \ No newline at end of property Index: src/test/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/TokenizerTest.java =================================================================== --- src/test/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/TokenizerTest.java (revision 0) +++ src/test/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/TokenizerTest.java (working copy) @@ -0,0 +1,63 @@ +package org.tuckey.web.filters.urlrewrite.substitution.interpreter; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class TokenizerTest { + + @Test + public void testTokenizer() { + doTokenSizeTest("${escapePath:UTF-8:a b c : other / path}", 9); + } + + @Test + public void testTokenizer2() { + doTokenSizeTest("abcd${escapePath:UTF-8:a b c : other / path}", 10); + } + + @Test + public void testTokenizer3() { + doTokenSizeTest("/test/${upper:$1}/${upper:$1}.html", 13); + } + + @Test + public void testTokenizer4() { + doTokenSizeTest("/search/${escapePath:${unescape:$1}}", 10); + } + + @Test + public void testTokenizer5() { + doTokenSizeTest("/search/${escapePath:${unescape:$1}}/hoge/${unescape:$1}", 16); + } + + @Test + public void testTokenizer6() { + doTokenSizeTest("/search/${escapePath:${unescape:$1}}/hoge/${escapePath:${unescape:$1}}", 20); + } + + @Test + public void testTokenizer7() { + doTokenSizeTest("/search/${escapePath:${unescape:$1}}/hoge/${escapePath:${unescape:$1}}abced:hoge", 23); + } + + @Test + public void testTokenizer8() { + doTokenSizeTest("/search/${trim: abc ${lower:Hello World} }/hoge/", 13); + } + + @Test + public void testTokenizer9() { + doTokenSizeTest("/file/hogehoge?fuga=$1&type=${escape:$2}&url=%{request-url}hogehoge&", 8); + doTokenSizeTest("${escape:$2}&url=%{request-url}", 6); + doTokenSizeTest("url=%{request-url}${escape:$2}", 6); + doTokenSizeTest("url=%{request-url}${escape:%{request-url}}", 6); + doTokenSizeTest("url=%{request-url}${escape:%{request-url}}%{request-url}", 7); + } + + private void doTokenSizeTest(String pattern, int size) { + Tokenizer target = new Tokenizer(pattern); + assertEquals(size, target.tokens.size()); + + } +} Property changes on: src/test/java/org/tuckey/web/filters/urlrewrite/substitution/interpreter/TokenizerTest.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Id Author Date Revision HeadURL \ No newline at end of property Index: src/test/java/org/tuckey/web/filters/urlrewrite/utils/FunctionReplacerTest.java =================================================================== --- src/test/java/org/tuckey/web/filters/urlrewrite/utils/FunctionReplacerTest.java (revision 442) +++ src/test/java/org/tuckey/web/filters/urlrewrite/utils/FunctionReplacerTest.java (working copy) @@ -95,4 +95,21 @@ assertEquals("aFOOBAR b", FunctionReplacer.replace("a${upper:${lower:fOObAR}} b")); } + public void testRecursive2() throws InvocationTargetException, IOException, ServletException { + String target = "a${upper:AbCd ${lower:fOObAR} 1234 } b"; + assertTrue(FunctionReplacer.containsFunction(target)); + assertEquals("aABCD FOOBAR 1234 b", FunctionReplacer.replace(target)); + } + + public void testRecursive3() throws InvocationTargetException, IOException, ServletException { + String target = "a${trim:AbCd ${lower:fOObAR} 1234 } b"; + assertTrue(FunctionReplacer.containsFunction(target)); + assertEquals("aAbCd foobar 1234 b", FunctionReplacer.replace(target)); + } + + public void testSeries() throws InvocationTargetException, IOException, ServletException { + String target = "a${upper:hoge} ${lower:fOObAR} b"; + assertTrue(FunctionReplacer.containsFunction(target)); + assertEquals("aHOGE foobar b", FunctionReplacer.replace(target)); + } }