From 33c81a70d155dca0a390f58270a0f402e251b91e Mon Sep 17 00:00:00 2001 From: "Ryan J. McDonough" Date: Sat, 3 Aug 2013 22:37:48 -0400 Subject: [PATCH] Refactoring to support reverse variable matching for issue #2 --- pom.xml | 54 ++++++++--- .../damnhandy/uri/template/Expression.java | 62 ++++++++++--- .../template/IllegalOperatorException.java | 30 ++++++ .../com/damnhandy/uri/template/Literal.java | 13 ++- .../MalformedUriTemplateException.java | 13 ++- .../damnhandy/uri/template/UriTemplate.java | 93 +++++++++++++++---- .../uri/template/UriTemplateComponent.java | 8 ++ .../uri/template/UriTemplateResolver.java | 77 ++++++++++++++- .../damnhandy/uri/template/impl/Operator.java | 6 +- .../uri/template/impl/UriTemplateParser.java | 11 +-- .../jackson/datatype/UriTemplateModule.java | 12 ++- .../jackson/datatype/package-info.java | 7 +- .../uri/template/TestReverseMatching.java | 35 +++++++ .../builder/TestExpressionBuilder.java | 8 +- 14 files changed, 366 insertions(+), 63 deletions(-) create mode 100644 src/main/java/com/damnhandy/uri/template/IllegalOperatorException.java create mode 100644 src/test/java/com/damnhandy/uri/template/TestReverseMatching.java diff --git a/pom.xml b/pom.xml index 899c9930..b5e95f8a 100755 --- a/pom.xml +++ b/pom.xml @@ -1,4 +1,5 @@ - + 4.0.0 org.sonatype.oss @@ -173,6 +174,25 @@ org.apache.maven.plugins maven-site-plugin 3.3 + + + lt.velykis.maven.skins + reflow-velocity-tools + 1.0.0 + + + + org.apache.velocity + velocity + 1.7 + + + + org.apache.maven.doxia + doxia-module-markdown + 1.3 + + @@ -245,8 +265,8 @@ org.apache.maven.plugins maven-compiler-plugin - 1.6 - 1.6 + 1.7 + 1.7 @@ -287,7 +307,7 @@ 2.9.1 true - 1.6 + 1.7 UTF-8 1g @@ -383,8 +403,8 @@ 4.11 test - - + + com.fasterxml.jackson.core @@ -392,14 +412,14 @@ ${jackson.version} true - + com.fasterxml.jackson.core jackson-databind ${jackson.version} true - + com.fasterxml.jackson.core jackson-annotations @@ -407,12 +427,12 @@ true - - com.google.guava - guava - 14.0.1 - test - + + com.google.guava + guava + 14.0.1 + test + @@ -427,6 +447,12 @@ 4.2.5 test + + org.apache.abdera + abdera-i18n + 1.1.3 + bundle + diff --git a/src/main/java/com/damnhandy/uri/template/Expression.java b/src/main/java/com/damnhandy/uri/template/Expression.java index 4534ecca..d58549e5 100644 --- a/src/main/java/com/damnhandy/uri/template/Expression.java +++ b/src/main/java/com/damnhandy/uri/template/Expression.java @@ -57,7 +57,8 @@ public class Expression extends UriTemplateComponent */ private static final Pattern VARNAME_REGEX = Pattern.compile("([\\w\\_\\.]|%[A-Fa-f0-9]{2})+"); /** - * A regex quoted string that matches the expression. For example: + * A regex quoted string that matches the expression for replacement in the + * expansion process. For example: *
     *          \Q{?query,number}\E
     * 
@@ -69,12 +70,20 @@ public class Expression extends UriTemplateComponent */ private Operator op; - + /** + * The character position where this expression occurs in the URI template + */ + private final int location; /** * The the parsed {@link VarSpec} instances found in the expression. */ private List varSpecs; + /** + * The Patter that would be used to reverse match this expression + */ + private Pattern matchPattern; + /** * Creates a new {@link Builder} to create a simple expression according @@ -255,10 +264,11 @@ public static Builder continuation(VarSpec...varSpec) * @param op * @param varSpecs */ - public Expression(final String rawExpression, int startPosition) throws MalformedUriTemplateException + public Expression(final String rawExpression, final int startPosition) throws MalformedUriTemplateException { super(startPosition); - this.parseRawExpression(rawExpression); + this.location = startPosition; + this.parseRawExpression(rawExpression); } /** @@ -275,6 +285,8 @@ public Expression(final Operator op, final List varSpecs) this.op = op; this.varSpecs = varSpecs; this.replacementPattern = Pattern.quote(toString()); + this.matchPattern = buildMatchingPattern(); + this.location = 0; } /** @@ -292,7 +304,14 @@ private void parseRawExpression(String rawExpression) throws MalformedUriTemplat String firstChar = token.substring(0, 1); if (UriTemplate.containsOperator(firstChar)) { - operator = Operator.fromOpCode(firstChar); + try + { + operator = Operator.fromOpCode(firstChar); + } + catch (IllegalOperatorException e) + { + throw new MalformedUriTemplateException("Invalid operator" , this.location, e); + } token = token.substring(1, token.length()); } String[] varspecStrings = token.split(","); @@ -315,7 +334,7 @@ private void parseRawExpression(String rawExpression) throws MalformedUriTemplat } catch (NumberFormatException e) { - throw new MalformedUriTemplateException("The prefix value for "+ pair[0]+ " was not a number", e); + throw new MalformedUriTemplateException("The prefix value for "+ pair[0]+ " was not a number", this.location, e); } } @@ -350,26 +369,45 @@ private void validateVarname(String varname) throws MalformedUriTemplateExceptio Matcher matcher = VARNAME_REGEX.matcher(varname); if(!matcher.matches()) { - throw new MalformedUriTemplateException("The variable name "+varname+" contains invalid characters"); + throw new MalformedUriTemplateException("The variable name "+varname+" contains invalid characters", this.location); } if(varname.contains(" ")) { - throw new MalformedUriTemplateException("The variable name "+varname+" cannot contain spaces (leading or trailing)"); + throw new MalformedUriTemplateException("The variable name "+varname+" cannot contain spaces (leading or trailing)", this.location); } } + /** + * + * + * @return + */ + private Pattern buildMatchingPattern() + { + StringBuilder b = new StringBuilder(); + for(VarSpec v : getVarSpecs()) + { + b.append("(?<").append(v.getVariableName()).append(">[^\\/]+)"); + } + return Pattern.compile(b.toString()); + } + /** * Returns a string that contains a regular expression that matches the * URI template expression. - * + * * @return */ - public String buildMatchingPattern() + @Override + public Pattern getMatchPattern() { - StringBuilder builder = new StringBuilder(); - return builder.toString(); + if(this.matchPattern == null) + { + this.matchPattern = buildMatchingPattern(); + } + return this.matchPattern; } /** * Get the replacementToken. diff --git a/src/main/java/com/damnhandy/uri/template/IllegalOperatorException.java b/src/main/java/com/damnhandy/uri/template/IllegalOperatorException.java new file mode 100644 index 00000000..41f2c88f --- /dev/null +++ b/src/main/java/com/damnhandy/uri/template/IllegalOperatorException.java @@ -0,0 +1,30 @@ +/* + * + * + */ +package com.damnhandy.uri.template; + +/** + * A IllegalOperatorException. + * + * @author Ryan J. McDonough + * @version $Revision: 1.1 $ + */ +public class IllegalOperatorException extends Exception +{ + + /** The serialVersionUID */ + private static final long serialVersionUID = 7874128076415224267L; + + + /** + * Create a new IllegalOperatorException. + * + * @param message + */ + public IllegalOperatorException(String message) + { + super(message); + } + +} diff --git a/src/main/java/com/damnhandy/uri/template/Literal.java b/src/main/java/com/damnhandy/uri/template/Literal.java index fbc8c619..38c85466 100644 --- a/src/main/java/com/damnhandy/uri/template/Literal.java +++ b/src/main/java/com/damnhandy/uri/template/Literal.java @@ -4,6 +4,8 @@ */ package com.damnhandy.uri.template; +import java.util.regex.Pattern; + /** * Represents the non-expression parts of a URI Template * @@ -16,9 +18,11 @@ public class Literal extends UriTemplateComponent /** The serialVersionUID */ private static final long serialVersionUID = 6011009312823496878L; - + private final String value; + private final Pattern matchPattern; + /** * Create a new Literal. * @@ -27,6 +31,7 @@ public Literal(final String value, int startPosition) { super(startPosition); this.value = value; + this.matchPattern = Pattern.compile(Pattern.quote(getValue())); } @Override @@ -41,4 +46,10 @@ public String toString() return value; } + @Override + public Pattern getMatchPattern() + { + return this.matchPattern; + } + } diff --git a/src/main/java/com/damnhandy/uri/template/MalformedUriTemplateException.java b/src/main/java/com/damnhandy/uri/template/MalformedUriTemplateException.java index 9c08e94f..f048fc79 100644 --- a/src/main/java/com/damnhandy/uri/template/MalformedUriTemplateException.java +++ b/src/main/java/com/damnhandy/uri/template/MalformedUriTemplateException.java @@ -17,7 +17,7 @@ /** * Raised when the the template processor encounters an issue parsing the URI template string. It indicates - * the template contains an expression that is malformed. + * the either the template itself is malformed, or an expression within the template is malformed. * * @author Ryan J. McDonough * @version $Revision: 1.1 $ @@ -29,15 +29,17 @@ public class MalformedUriTemplateException extends Exception /** The serialVersionUID */ private static final long serialVersionUID = 5883174281977078450L; + private int location; /** * Create a new UriTemplateParseException. * * @param message * @param cause */ - public MalformedUriTemplateException(String message, Throwable cause) + public MalformedUriTemplateException(String message, int location, Throwable cause) { super(message, cause); + this.location = location; } /** @@ -45,9 +47,14 @@ public MalformedUriTemplateException(String message, Throwable cause) * * @param message */ - public MalformedUriTemplateException(String message) + public MalformedUriTemplateException(String message, int location) { super(message); + this.location = location; } + public int getLocation() + { + return this.location; + } } diff --git a/src/main/java/com/damnhandy/uri/template/UriTemplate.java b/src/main/java/com/damnhandy/uri/template/UriTemplate.java index 19b46054..f33ba2d8 100644 --- a/src/main/java/com/damnhandy/uri/template/UriTemplate.java +++ b/src/main/java/com/damnhandy/uri/template/UriTemplate.java @@ -30,6 +30,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.regex.Pattern; import com.damnhandy.uri.template.impl.Modifier; import com.damnhandy.uri.template.impl.Operator; @@ -96,7 +97,7 @@ public static enum Encoding { * */ static final char[] OPERATORS = - {'+', '#', '.', '/', ';', '?', '&'}; + {'+', '#', '.', '/', ';', '?', '&', '!', '='}; /** * @@ -111,23 +112,36 @@ public static enum Encoding { } } - protected String template; + /** + * The URI template String + */ + private String template; + + /** + * A regex string that matches the a URI to the template pattern + */ + private Pattern reverseMatchPattern; /** * The collection of values that will be applied to the URI expression in the * expansion process. */ - protected Map values = new HashMap(); + private Map values = new HashMap(); /** * */ - protected LinkedList components; + private LinkedList components; /** * */ - protected Expression[] expressions; + private Expression[] expressions; + + /** + * + */ + private String[] variables; /** * @@ -218,6 +232,47 @@ public static UriTemplateBuilder fromTemplate(UriTemplate base) throws Malformed } + /** + * Returns the number of expressions found in this template + * + * @return + */ + public int expressionCount() + { + return expressions.length; + } + + /** + * FIXME Comment this + * + * @return + */ + public Expression[] getExpressions() + { + return expressions; + } + + /** + * Returns the list of variable names in this template. + * + * @return + */ + public String[] getVariables() + { + if(variables == null) + { + List vars = new ArrayList<>(); + for(Expression e : getExpressions()) + { + for(VarSpec v : e.getVarSpecs()) + { + vars.add(v.getVariableName()); + } + } + variables = vars.toArray(new String[vars.size()]); + } + return variables; + } /** * Parse the URI template string into the template model. * @@ -257,26 +312,30 @@ private void buildTemplateStringFromComponents() } this.template = b.toString(); } - /** - * Returns the number of expressions found in this template - * - * @return - */ - public int expressionCount() + + private void buildReverssMatchRegexFromComponents() { - return expressions.length; + StringBuilder b = new StringBuilder(); + for(UriTemplateComponent c : components) + { + b.append("(").append(c.getMatchPattern()).append(")"); + } + this.reverseMatchPattern = Pattern.compile(b.toString()); } - + /** * FIXME Comment this - * + * * @return */ - public Expression[] getExpressions() + protected Pattern getReverseMatchPattern() { - return expressions; + if(this.reverseMatchPattern == null) + { + buildReverssMatchRegexFromComponents(); + } + return this.reverseMatchPattern; } - /** * Expands the given template string using the variable replacements * in the supplied {@link Map}. diff --git a/src/main/java/com/damnhandy/uri/template/UriTemplateComponent.java b/src/main/java/com/damnhandy/uri/template/UriTemplateComponent.java index 9cd28cdc..7371bfc6 100644 --- a/src/main/java/com/damnhandy/uri/template/UriTemplateComponent.java +++ b/src/main/java/com/damnhandy/uri/template/UriTemplateComponent.java @@ -5,6 +5,7 @@ package com.damnhandy.uri.template; import java.io.Serializable; +import java.util.regex.Pattern; /** * A Component. @@ -40,6 +41,13 @@ public int getStartPosition() return startPosition; } + /** + * Returns a string that contains a regular expression that matches the + * URI template expression. + * + * @return + */ + public abstract Pattern getMatchPattern(); /** * Get the endPosition. * diff --git a/src/main/java/com/damnhandy/uri/template/UriTemplateResolver.java b/src/main/java/com/damnhandy/uri/template/UriTemplateResolver.java index aa422161..c124004f 100644 --- a/src/main/java/com/damnhandy/uri/template/UriTemplateResolver.java +++ b/src/main/java/com/damnhandy/uri/template/UriTemplateResolver.java @@ -14,9 +14,18 @@ * limitations under the License. */ package com.damnhandy.uri.template; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + /** * - * A UriTemplateResolver. + * Utility used to match a URI to a collection of {@link UriTemplate} instances. + * * * @author Ryan J. McDonough * @version $Revision: 1.1 $ @@ -25,4 +34,70 @@ public class UriTemplateResolver { + private List templates = new ArrayList<>(); + + /** + * + * + * @param templateStrings + * @throws MalformedUriTemplateException + */ + public void addUriTemplates(final String...templateStrings) throws MalformedUriTemplateException + { + for(String template : templateStrings) + { + addUriTemplate(template); + } + } + /** + * Creates a {@link UriTemplate} from the input string and adds it + * to the collection of templates to match against. + * + * @param templateString + * @return + * @throws MalformedUriTemplateException + */ + public boolean addUriTemplate(final String templateString) throws MalformedUriTemplateException + { + return this.addUriTemplate(UriTemplate.fromTemplate(templateString)); + } + /** + * Adds a {@link UriTemplate} instance to an internal list of + * templates to match against. + * + * @param template + * @return + */ + public boolean addUriTemplate(final UriTemplate template) + { + return templates.add(template); + } + + + /** + * Matches the input URI against the collection of {@link UriTemplate}'s + * + * @param uri + * @return + */ + public Map match(String uri) + { + for(UriTemplate template : templates) + { + Pattern p = template.getReverseMatchPattern(); + System.out.println(p.pattern()); + Matcher m = p.matcher(uri); + if (m.matches()) + { + Map params = new HashMap(m.groupCount()); + for (String param : template.getVariables()) + { + params.put(param, m.group(param)); + } + System.out.println(params); + return params; + } + } + return null; + } } diff --git a/src/main/java/com/damnhandy/uri/template/impl/Operator.java b/src/main/java/com/damnhandy/uri/template/impl/Operator.java index 9782101f..1d980d4b 100644 --- a/src/main/java/com/damnhandy/uri/template/impl/Operator.java +++ b/src/main/java/com/damnhandy/uri/template/impl/Operator.java @@ -17,7 +17,7 @@ import static com.damnhandy.uri.template.UriTemplate.DEFAULT_SEPARATOR; -import com.damnhandy.uri.template.MalformedUriTemplateException; +import com.damnhandy.uri.template.IllegalOperatorException; import com.damnhandy.uri.template.UriTemplate.Encoding; /** @@ -157,7 +157,7 @@ public String getPrefix() * @param opCode * @return */ - public static Operator fromOpCode(String opCode) throws MalformedUriTemplateException + public static Operator fromOpCode(String opCode) throws IllegalOperatorException { for (Operator op : Operator.values()) { @@ -167,7 +167,7 @@ public static Operator fromOpCode(String opCode) throws MalformedUriTemplateExce } else if (opCode.equalsIgnoreCase("!") || opCode.equalsIgnoreCase("=")) { - throw new MalformedUriTemplateException(opCode + " is not a valid operator."); + throw new IllegalOperatorException(opCode + " is not a valid operator."); } } return null; diff --git a/src/main/java/com/damnhandy/uri/template/impl/UriTemplateParser.java b/src/main/java/com/damnhandy/uri/template/impl/UriTemplateParser.java index 415f36ac..667d97c9 100644 --- a/src/main/java/com/damnhandy/uri/template/impl/UriTemplateParser.java +++ b/src/main/java/com/damnhandy/uri/template/impl/UriTemplateParser.java @@ -135,8 +135,7 @@ private void endTemplate(int position) throws MalformedUriTemplateException startedTemplate = false; if (expressionCaptureOn) { - throw new MalformedUriTemplateException("Template scanning complete, but the start of an expression at " - + startPosition + " was never terminated"); + throw new MalformedUriTemplateException("The expression at position " + startPosition + " was never terminated", startPosition); } } @@ -176,7 +175,7 @@ private void endLiteral(int position) throws MalformedUriTemplateException // we started capturing a literal but never actually collected anything yet. if(buffer != null) { - components.add(new Literal(buffer.toString(), startPosition)); + components.add(new Literal(buffer.toString(), this.startPosition)); literalCaptureOn = false; buffer = null; } @@ -202,7 +201,7 @@ private void startExpression(int position) throws MalformedUriTemplateException { throw new MalformedUriTemplateException("A new expression start brace found at " + position - + " but another unclosed expression was found at " + startPosition); + + " but another unclosed expression was found at " + startPosition, position); } literalCaptureOn = false; expressionCaptureOn = true; @@ -228,10 +227,10 @@ private void endExpression(int position) throws MalformedUriTemplateException { throw new MalformedUriTemplateException("Expression close brace was found at position " + position - + " yet there was no start brace."); + + " yet there was no start brace.", position); } expressionCaptureOn = false; - components.add(new Expression(buffer.toString(), startPosition)); + components.add(new Expression(buffer.toString(), this.startPosition)); buffer = null; } else diff --git a/src/main/java/com/damnhandy/uri/template/jackson/datatype/UriTemplateModule.java b/src/main/java/com/damnhandy/uri/template/jackson/datatype/UriTemplateModule.java index e456f2d8..b65b8101 100644 --- a/src/main/java/com/damnhandy/uri/template/jackson/datatype/UriTemplateModule.java +++ b/src/main/java/com/damnhandy/uri/template/jackson/datatype/UriTemplateModule.java @@ -6,10 +6,20 @@ import com.damnhandy.uri.template.UriTemplate; import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; /** - * A UriTemplateModule. + * Uri Template module for Jackson. To use it, simpley register it + * with an {@link ObjectMapper} like so: + * + *
+ * ObjectMapper mapper = new ObjectMapper();
+ * mapper.registerModule(new UriTemplateModule());
+ * 
+ * + * Any mapped JSON property that is a {@link UriTemplate} will be + * serialized or deserialized properly. * * @author Ryan J. McDonough * @version $Revision: 1.1 $ diff --git a/src/main/java/com/damnhandy/uri/template/jackson/datatype/package-info.java b/src/main/java/com/damnhandy/uri/template/jackson/datatype/package-info.java index 00ee3287..7b714170 100644 --- a/src/main/java/com/damnhandy/uri/template/jackson/datatype/package-info.java +++ b/src/main/java/com/damnhandy/uri/template/jackson/datatype/package-info.java @@ -18,9 +18,14 @@ * @JsonDeserialize(using = UriTemplateDeserializer.class) * @JsonSerialize(using = UriTemplateSerializer.class) * private UriTemplate template; + * ... * * - *

Both option will yeild the same results

+ *

Both option will yeild the same results + *

+ * + * * @author Ryan J. McDonough * @version $Revision: 1.1 $ */ diff --git a/src/test/java/com/damnhandy/uri/template/TestReverseMatching.java b/src/test/java/com/damnhandy/uri/template/TestReverseMatching.java new file mode 100644 index 00000000..eb353b7e --- /dev/null +++ b/src/test/java/com/damnhandy/uri/template/TestReverseMatching.java @@ -0,0 +1,35 @@ +/* + * + * + */ +package com.damnhandy.uri.template; + + + +import java.util.Map; + +import org.junit.Assert; +import org.junit.Test; + +/** + * A TestReverseMatching. + * + * @author Ryan J. McDonough + * @version $Revision: 1.1 $ + */ +public class TestReverseMatching +{ + + @Test + public void testLevel1RevsereMatch() throws Exception + { + UriTemplateResolver resolver = new UriTemplateResolver(); + resolver.addUriTemplates("http://example.com/{foo}", + "http://example.com/{foo}/{bar}", + "http://example.com/{foo}/{bar}{?things}"); + + Map values = resolver.match("http://example.com/boo/moo"); + Assert.assertNotNull(values); + } + +} diff --git a/src/test/java/com/damnhandy/uri/template/builder/TestExpressionBuilder.java b/src/test/java/com/damnhandy/uri/template/builder/TestExpressionBuilder.java index 820ddf46..d4c65982 100644 --- a/src/test/java/com/damnhandy/uri/template/builder/TestExpressionBuilder.java +++ b/src/test/java/com/damnhandy/uri/template/builder/TestExpressionBuilder.java @@ -97,14 +97,14 @@ public void testContinuation() throws Exception @Test public void testMultipleExpressions() throws Exception { - Expression e = Expression.simple(var("foo", 1), var("foo"), var("thing", true)).build(); - Assert.assertEquals("{foo:1,foo,thing*}", e.toString()); + Expression e = Expression.simple(var("foo", 1), var("bar"), var("thing", true)).build(); + Assert.assertEquals("{foo:1,bar,thing*}", e.toString()); } @Test public void testMultipleExpressionsAndLiteralValues() throws Exception { - Expression e = Expression.simple(var("foo", 1), var("foo"), var("thing", true)).build(); - Assert.assertEquals("{foo:1,foo,thing*}", e.toString()); + Expression e = Expression.simple(var("foo", 1), var("bar"), var("thing", true)).build(); + Assert.assertEquals("{foo:1,bar,thing*}", e.toString()); } }