Created
December 13, 2011 23:32
-
-
Save diosmosis/1474468 to your computer and use it in GitHub Desktop.
Scala Experiment Iteration 0
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import scala.collection.mutable.HashMap | |
/** Represents a segment in a router's tree of valid paths. Describes exactly what | |
* path segments are allowed to come after this one. | |
*/ | |
class RouteTreeNode(pathSegment:String) { | |
/** The segment this node represents. */ | |
private val segment = pathSegment | |
/** The child nodes representing possible paths this segment can lead to. */ | |
private val children:HashMap[String, RouteTreeNode] = new HashMap[String, RouteTreeNode]() | |
/** The callback associated with this node. | |
* | |
* If this member is not null, then the path that leads to this node is a | |
* valid path. | |
*/ | |
var callback:(Array[String]) => Unit = null | |
/** Adds the child to this node's list of children. | |
* | |
* @param child The child node to add. If there already is a child node w/ the | |
* same segment as this node, it is overwritten. | |
* @return returns the child node for convenience. | |
*/ | |
def addChild(child:RouteTreeNode):RouteTreeNode = { | |
children(child.segment) = child | |
return child | |
} | |
/** Returns the child node with the specified segment, or null if there is none. | |
* | |
* @param segment The path segment. | |
* @return The child node associated with segment or null. | |
*/ | |
def getChild(segment:String):RouteTreeNode = { | |
return children.getOrElse(segment, null) | |
} | |
} | |
/** Maintains and traverses a tree of "/my/path/to/something"-like paths. | |
* | |
* A Router will associate URL paths, like /this/is/a/path, with callbacks and | |
* attempt to invoke these callbacks when given an arbitrary path. | |
* | |
* Router will allow you to use wildcards in place for path segments. For | |
* example, /this/:will/match will match both "/this/will/match" and | |
* "/this/abc/match". | |
*/ | |
class Router { | |
/** The root node of the Router's route tree. Holds every valid path & the | |
* callbacks associated with them. | |
*/ | |
private var routeTree:RouteTreeNode = new RouteTreeNode("") | |
/** The callback invoked when routing fails. */ | |
private var onError:(Array[String]) => Unit = null | |
/** Invokes the callback associated with the given path, or the onError | |
* callback if the path is invalid. | |
* | |
* @param path The path to route. | |
* @return True if the route was successful, false if otherwise. | |
*/ | |
def route(path: String):Boolean = { | |
// get the path segments & remove the empty segments | |
val segments = path.split("/").filter((s) => s.length > 0) | |
// travel through the route tree, segment by segment | |
var node = routeTree | |
for (segment <- segments) { | |
// look for a child node that matches the segment exactly | |
var child = node.getChild(segment) | |
// if there is no child node that matches the segment exactly, try | |
// looking for a wildcard | |
if (child == null) { | |
child = node.getChild("*") | |
} | |
// no match? invoke onError | |
if (child == null) { | |
if (onError != null) onError(segments) | |
return false | |
} | |
node = child | |
} | |
// if the specific node has no callback, it is not a valid path end | |
if (node.callback == null) { | |
if (onError != null) onError(segments) | |
return false | |
} | |
node.callback(segments) | |
return true | |
} | |
/** Associates the supplied path with the supplied callback. | |
* | |
* @param path The URL path to associate with. Must be of the format: "/a/sample/path". | |
* Can use wildcards in the format of "/a/path/:wildcard-name". | |
* @param callback The callback to run when a matched path is routed. This callback | |
* will be supplied the path segments when invoked. | |
* @return The Router instance for convenience. | |
*/ | |
def addRoute(path: String, callback: (Array[String]) => Unit):Router = { | |
// get the path segments & remove the empty segments | |
val segments = path.split("/").filter((s) => s.length > 0) | |
// travel the route tree and add missing nodes as they come up | |
var node = routeTree | |
for (segment <- segments) { | |
var realSegment = segment | |
// if segment is a wildcard, replace it with the '*' value. right now, | |
// we don't care about the wildcard's name | |
if (realSegment.startsWith(":")) { | |
realSegment = "*" | |
} | |
val child = node.getChild(realSegment) | |
if (child == null) { | |
node = node.addChild(new RouteTreeNode(realSegment)) | |
} else { | |
node = child | |
} | |
} | |
node.callback = callback | |
return this | |
} | |
/** Sets the callback that is run when routing fails. | |
* | |
* @param callback The callback. | |
* @return The Router instance for convenience. | |
*/ | |
def setOnError(callback: (Array[String]) => Unit):Router = { | |
onError = callback | |
return this | |
} | |
} | |
/** Testing singleton. */ | |
object Test { | |
def main(args: Array[String]) = { | |
val router = new Router() | |
router.addRoute("/test/route", (path: Array[String]) => println("At /test/route")) | |
.addRoute("/pages/:name", (path: Array[String]) => println("At /pages/:name w/ name=" + path(1))) | |
.addRoute("/things/:with/:widgets", | |
(path: Array[String]) => println("At /things/:with/:widgets w/ with=" + path(1) + " & widgets=" + path(2))) | |
.setOnError((path: Array[String]) => println("Route failed: /" + path.reduceLeft(_ + "/" + _))) | |
val routesToTest = List("/test/route", "/pages/my_page", "/things/w/i", "/pages/my-other-page", "/things/w") | |
routesToTest.foreach((path) => router.route(path)) | |
} | |
} | |
Test.main(args) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment