Recently I read Michael Marth's article Extending JCR with JRuby. Michael shows how to extend existing JCR interfaces with JRuby. He adds a method getAllNodes to JCR's getAllNodes method returns the nodes of the whole sub-tree of the current node. He further adds a method to Repository which makes using the getDescriptor method more convenient.
In this article I show how the same can be done with Scala. In contrast to JRuby, Scala is a statically typed language. Re-opening existing classes is not directly possible. However, using implicit definitions and the Pimp my Library pattern, a similar effect can be achieved while fully maintaining type safety.
First we add two functions to Node. The childNodes function returns a Scala Iterator over the direct child nodes of a node. This function is just a wrapper around the NodeIterator returned by getNodes. This is necessary since the allChildNodes function needs the richer functionality provided by Scala's iterators.
implicit def richNode(node: Node) = new RichNode(node)
class RichNode(node: Node) {
def childNodes = new Iterator[Node] {
val it = node.getNodes
def hasNext = it.hasNext
def next = it.nextNode
}
def allChildNodes: Iterator[Node] = {
(for(n <- childNodes)
yield n.allChildNodes)
.foldLeft[Iterator[Node]](childNodes)(_ ++ _)
}
}
The for expression in allChildNodes recursively retrieves all child nodes of the direct child nodes of a node. The returned iterators are subsequently concatenated together by passing them to the foldLeft function together with the direct child nodes and the concatenation operator ++.
We can now use
for (n <- node.allChildNodes) println(n.getName)
to print the names of all nodes in a given node's sub-tree. When Scala cannot find the allChildNodes function on Node, it automatically converts the node to an instance of a RichNode as long as the implicit conversion function richNode is in scope.
We can now extend Repository in a similar way:
implicit def richRepository(repository: Repository) = new {
def descriptor(name: String) = {
if (repository.getDescriptor(name) == java.lang.Boolean.toString(true))
true
else if (repository.getDescriptor(name) == java.lang.Boolean.toString(false))
false
else
repository.getDescriptor(name)
}
}
Note the use of an anonymous class instead of explicitly declaring a RichRepository class. (Quiz: is it possible to use an anonymous class instead of RichNode in the first example too? How?)
We can now check for observation support like so:
if (repository.descriptor(Repository.OPTION_OBSERVATION_SUPPORTED) == true) println(Repository.OPTION_OBSERVATION_SUPPORTED)
Although it might look strange, explicitly comparing to true is necessary here. The reason is, that the return type of the descriptor function is not a Boolean but rather the least upper bound of the types in all branches of the if-else statement. The least upper bound for types Boolean and String is the type Any.
