Skip to content

Commit

Permalink
Added support for reverse variable matching
Browse files Browse the repository at this point in the history
Bumped Java version to 8
  • Loading branch information
nrktkt committed Aug 15, 2016
1 parent 650efe5 commit 05b8e06
Show file tree
Hide file tree
Showing 8 changed files with 293 additions and 81 deletions.
3 changes: 1 addition & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
language: java
jdk:
- openjdk7
- oraclejdk7
- openjdk8
- oraclejdk8
sudo: false
install: ./mvnw clean
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,17 @@ This will yield the following URL template string:

This API is still a work in progress an feedback is appreciated.

## Reverse Variable Matching

A `UriTemplate` can also be used to extract variables from a URI like this:

```java
UriTemplate template = UriTemplate.fromTemplate("https://example.com/collection{/id}{?orderBy}");
template.setFrom("https://example.com/collection/9?orderBy=age");
System.out.println(template.get("id")); // 9
System.out.println(template.get("orderBy")); // age
```

## Using with HTTP Clients

The API can be used with existing HTTP frameworks like the most excellent [Async Http Client](https://github.com/sonatype/async-http-client). Using the [GitHub API](http://developer.github.com/v3/repos/commits/), we can use the a `UriTemplate` to create a URI to look at this repository:
Expand Down
24 changes: 0 additions & 24 deletions notes.md

This file was deleted.

4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,8 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>

Expand Down
48 changes: 48 additions & 0 deletions src/main/java/com/damnhandy/uri/template/Either.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.damnhandy.uri.template;

import static java.util.Objects.requireNonNull;

/**
* Created by nfischer on 8/14/2016.
*/
public class Either<Left, Right> {
public final Left left;
public final Right right;

private Either(Left left, Right right) {
this.left = left;
this.right = right;
}

public static <Left, Right> Either<Left, Right> left(Left left){
requireNonNull(left);
return new Either<>(left, null);
}

public static <Left, Right> Either<Left, Right> right(Right right){
requireNonNull(right);
return new Either<>(null, right);
}

public Object get(){
if(left == null)
return right;
else return left;
}

public boolean isLeft(){
return left != null;
}

public boolean isRight(){
return !isLeft();
}

@Override
public String toString() {
return "Either{" +
"left=" + left +
", right=" + right +
'}';
}
}
135 changes: 95 additions & 40 deletions src/main/java/com/damnhandy/uri/template/Expression.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@
import com.damnhandy.uri.template.impl.Operator;
import com.damnhandy.uri.template.impl.VarSpec;

import java.util.ArrayList;
import java.util.List;
import java.security.SecureRandom;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.damnhandy.uri.template.Either.*;
import static java.util.stream.Collectors.toList;

/**
* <p>
* An Expression represents the text between '{' and '}', including the enclosing
Expand Down Expand Up @@ -82,6 +85,14 @@ public class Expression extends UriTemplateComponent
*/
private Pattern matchPattern;

private String groupName = uid();

private static Random RANDOM = new SecureRandom();
private static String uid(){
byte[] b = new byte[12];
RANDOM.nextBytes(b);
return 'x'+Base64.getUrlEncoder().encodeToString(b).replace('-', '0').replace('_', '9');
}

/**
* Creates a new {@link Builder} to create a simple expression according
Expand Down Expand Up @@ -350,57 +361,101 @@ else if (varname.lastIndexOf(Modifier.EXPLODE.getValue()) > 0)
this.varSpecs = varspecs;
}

public Map<String, Either<String, List<String>>> variables(String uriPart){
if(getOperator().isNamed()){
return matchParameters(uriPart);
}else{
return matchSegments(uriPart);
}
}

private Map<String, Either<String, List<String>>> matchParameters(String part){
final String separator = getOperator().getSeparator();
List<String> varNames = getVarSpecs().stream() // todo use the varspecs to account for explosions and prefix mods
.map(VarSpec::getVariableName)
.collect(toList());

StringBuilder regex;
Map<String, List<String>> results = new HashMap<>();

private Pattern buildMatchingPattern()
{
StringBuilder b = new StringBuilder();
StringBuilder varNameRex = new StringBuilder("(?<key>");
for(String varName:varNames){
varNameRex.append(Pattern.quote(varName)).append('|');
}
varNameRex.deleteCharAt(varNameRex.length()-1);
varNameRex.append(')');

if(getOperator() != Operator.RESERVED) //todo expression prefix vs expansion prefix
{
final String prefix = getOperator().getPrefix();
if(prefix.length() > 0){
b.append('\\').append(getOperator().getPrefix());
}
regex = new StringBuilder()
.append(varNameRex)
.append('=')
.append("(?<value>[^")
.append(separator) // todo replace this with character classes based on allowed encoding
.append("]*)");

Pattern pattern = Pattern.compile(regex.toString());
Matcher matcher = pattern.matcher(part);

while(matcher.find()){
String key = matcher.group(1);
String value = matcher.group(2);

List<String> values = results.getOrDefault(key, new ArrayList<>());
values.add(value);

results.put(key, values);
}

String unreserved = "[\\w-\\d.~]";
String reserved = "[:\\/?#\\[\\]@!$&'()*+;=]"; //todo removing , for now
String encoded = "%[A-Fa-f\\d]{2}";
Map<String, Either<String, List<String>>> ret = new HashMap<>();
results.forEach((k,v) -> ret.put(k, v.size() == 1 ? left(v.get(0)) : right(v)));

return ret;
}

for(VarSpec v : getVarSpecs()){
private Map<String, Either<String, List<String>>> matchSegments(String part){
final String prefix = getOperator().getPrefix();
final String separator = getOperator().getSeparator();
List<String> varNames = getVarSpecs().stream()
.map(VarSpec::getVariableName)
.collect(toList());

if(getOperator() == Operator.MATRIX
|| getOperator() == Operator.CONTINUATION
|| getOperator() == Operator.QUERY)
b.append(v.getVariableName())
.append('=');
StringBuilder regex = prefix == null || prefix.isEmpty() ? new StringBuilder() : new StringBuilder('\\').append(prefix);
Map<String, Either<String, List<String>>> results = new HashMap<>();

b
.append("(?<")
.append(v.getVariableName())
.append('>');
for(String varName:varNames){
regex.append("(?<").append(Pattern.quote(varName)).append(">[^").append(separator).append("]*)").append(separator);
}

if(getOperator().getEncoding() == UriTemplate.Encoding.U)
b.append("(?:" + unreserved + "|" + encoded + ')');
else
b
.append("(?:")
.append(unreserved)
.append('|')
.append(reserved)
.append('|')
.append(encoded)
.append(')');
b.append('*'); //todo this needs to look at the varspec to check for prefix modifier
b
.append(')')
.append(getOperator().getSeparator())
.append('?');
regex.deleteCharAt(regex.length() -1);

Pattern pattern = Pattern.compile(regex.toString());
Matcher matcher = pattern.matcher(part);

if (!matcher.matches())
throw new RuntimeException("doesn't match");

for(String varName:varNames){
results.put(varName, left(matcher.group(varName)));
}

return results;
}

public String getGroupName(){
return this.groupName;
}

// todo improve this
private Pattern buildMatchingPattern()
{
String prefix = getOperator().getPrefix();
prefix = prefix.equals("+") ? "" : prefix;
StringBuilder b = new StringBuilder("(?<")
.append(groupName)
.append('>')
.append('\\')
.append(prefix)
.append(".{1,9001}")
.append(')');
return Pattern.compile(b.toString());
}

Expand Down
29 changes: 17 additions & 12 deletions src/main/java/com/damnhandy/uri/template/UriTemplate.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
Expand Down Expand Up @@ -338,7 +339,7 @@ private void buildReverseMatchRegexFromComponents()
StringBuilder b = new StringBuilder();
for (UriTemplateComponent c : components)
{
b.append("(").append(c.getMatchPattern()).append(")");
b.append('(').append(c.getMatchPattern()).append(')');
}
this.reverseMatchPattern = Pattern.compile(b.toString());
}
Expand Down Expand Up @@ -551,6 +552,18 @@ public UriTemplate set(String variableName, Date value)
return this;
}

public UriTemplate setFrom(String uri){
Pattern pattern = getReverseMatchPattern();
Matcher matcher = pattern.matcher(uri);
matcher.find();
for(Expression expression :expressions){
String part = matcher.group(expression.getGroupName());
expression.variables(part).forEach(
(k, v) -> this.set(k, v.get()));
}
return this;
}

/**
* Adds the name/value pairs in the supplied {@link Map} to the collection
* of values within this URI template instance.
Expand Down Expand Up @@ -1148,16 +1161,8 @@ private int[] getIndexForPartsWithNullsFirstIfQueryOrRegularSequnceIfNot(final E
return index;
}

public String toString(){
return this.expand();
}



/**
*
*
* @return
*/
// public String getRegexString()
// {
// return null;
// }
}
Loading

0 comments on commit 05b8e06

Please sign in to comment.