/**
*
*/
package com.trendrr.strest.server.routing;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.trendrr.oss.StringHelper;
/**
*
* Creates a tree of uri mappings for fast lookup.
*
* will handle named wildcards in the rails style:
*
* /object/:name/:id
*
* will match
* /object/my_object/2009
*
* Wildcards will only match on / boundaries. so /my_object:id will not work.
*
*
* lookup should be constant time, no matter how many routes are in the system
*
*
* @author Dustin Norlander
* @created Jan 14, 2011
*
*/
public class RouteMatcher {
protected static Log log = LogFactory.getLog(RouteMatcher.class);
protected TreeNode tree = new TreeNode();
protected HashMap<String, UriMapping> nonWildcardRoutes = new HashMap<String, UriMapping>();
protected HashSet<UriMapping> all = new HashSet<UriMapping>();
public static void main(String ...strings) {
RouteMatcher tree = new RouteMatcher();
//
// tree.addMapping(new UriMapping("/", DummyController.class));
// tree.addMapping(new UriMapping("/dustin", DummyController.class));
// tree.addMapping(new UriMapping("/billie", DummyController.class));
// tree.addMapping(new UriMapping("/dustin/:id", DummyController.class));
// tree.addMapping(new UriMapping("/dustin/:name/:id", DummyController.class));
// tree.addMapping(new UriMapping("/", DummyController.class));
//
// System.out.println(tree.find("/dustin/blah"));
// System.out.println(tree.find("/dustin/blah/asdf"));
// System.out.println(tree.find("/"));
//
}
/**
* Finds the matched route.
*
* returns null if no match is found.
*
* if multiple matches are found, first one is returned (no guarentee on ordering).
*
* if uri ends with .extension then that is trimmed and put as return_type into the params
* @param uri
* @return
*/
public MatchedRoute find(String uri) {
MatchedRoute m = new MatchedRoute();
String u = StringHelper.trim(uri, "/");
String extension = null;
{
String tmp[] = u.split("\\.");
if (tmp.length == 2) {
u = tmp[0];
extension = tmp[1];
}
}
UriMapping route = this.nonWildcardRoutes.get(u);
if (route == null) {
//check wildcard matches.
List<UriMapping> found = new ArrayList<UriMapping>();
List<String> words = new ArrayList<String>();
for (String w : u.split("\\/")) {
words.add(w);
}
tree.find(found, words);
if (found.isEmpty()) {
return null;
}
route = found.get(found.size()-1);
m.setParams(route.getWildCardMatches(words));
}
m.setMapping(route);
if (extension != null) {
m.getParams().put("return_type", extension);
}
return m;
}
/**
* adds a route mapping.
*
* TODO: this is synchronized, but not technically threadsafe, as a concurrent find could be happening. Should fix
*
* @param mapping
*/
public synchronized void addMapping(UriMapping mapping) {
if (!mapping.isWildCard()) {
this.nonWildcardRoutes.put(mapping.getRoute(), mapping);
}else if (mapping.getRoute().isEmpty()) {
tree.setMapping(mapping);
} else {
tree.addChildNode(mapping, mapping.getTokens());
}
this.all.add(mapping);
}
/**
* returns a list of all the mapped uris
* @return
*/
public Collection<UriMapping> getAll() {
return this.all;
}
}