Skip to content

Commit

Permalink
util-registry: Naive implementation to get the ball rolling
Browse files Browse the repository at this point in the history
Problem

We want to have a low-level API for the R* Registry, which codifies
the rules that we expect the "client libraries" that we actually
ship to people will use.

Solution

Nail down the API with a naive implementation first.  The naive
implementation is partially there because the data model is exactly
what I imagine it being in my head.

RB_ID=568274
  • Loading branch information
mosesn authored and jenkins committed Feb 23, 2015
1 parent dd14621 commit 310f6e1
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 1 deletion.
12 changes: 11 additions & 1 deletion project/Build.scala
Expand Up @@ -103,7 +103,7 @@ object Util extends Build {
sharedSettings ++
Unidoc.settings
) aggregate(
utilCore, utilCodec, utilCollection, utilCache, utilReflect,
utilRegistry, utilCore, utilCodec, utilCollection, utilCache, utilReflect,
utilLogging, utilTest, utilThrift, utilHashing, utilJvm, utilZk,
utilZkCommon, utilClassPreloader, utilBenchmark, utilApp,
utilEvents, utilStats, utilEval
Expand Down Expand Up @@ -274,6 +274,16 @@ object Util extends Build {
name := "util-logging"
).dependsOn(utilCore, utilApp, utilStats)


lazy val utilRegistry = Project(
id = "util-registry",
base = file("util-registry"),
settings = Project.defaultSettings ++
sharedSettings
).settings(
name := "util-registry"
)

lazy val utilStats = Project(
id = "util-stats",
base = file("util-stats"),
Expand Down
13 changes: 13 additions & 0 deletions util-registry/BUILD
@@ -0,0 +1,13 @@
maven_layout()

jar_library(name='util-registry',
dependencies=[
'util/util-core/src/main/scala',
]
)

jar_library(name='tests',
dependencies=[
'util/util-core/src/test/scala'
]
)
8 changes: 8 additions & 0 deletions util-registry/src/main/scala/BUILD
@@ -0,0 +1,8 @@
scala_library(name='scala',
provides = scala_artifact(
org = 'com.twitter',
name = 'util-registry',
repo = artifactory,
),
sources=rglobs('*.scala')
)
61 changes: 61 additions & 0 deletions util-registry/src/main/scala/com/twitter/registry/Registry.scala
@@ -0,0 +1,61 @@
package com.twitter.registry

import java.util.NoSuchElementException

private[registry] final case class Entry(key: Seq[String], value: String)

private[registry] object Entry {
val TupledMethod: ((Seq[String], String)) => Entry = (Entry.apply _).tupled
}

/**
* This is an expert-level API; it is not meant for end-users.
*
* The registry is a hierarchical key/value store, where all keys are sequences
* of Strings, and values are Strings.
*
* Keys and values must be non-control ascii, and must not contain the '/'
* character. If you pass in a key or value with an invalid character, it will
* silently be removed. If this makes your key clash with another key, it will
* overwrite.
*/
private[registry] trait Registry extends Iterable[Entry] {
/**
* Provides an iterator over the registry.
*
* It is the responsibility of the caller to synchronize if they would like to
* iterate in multiple threads, but the iterator is guaranteed not to change as
* it is called.
*/
def iterator(): Iterator[Entry]

/**
* Registers a value in the registry, and returns the old value (if any).
*/
def put(key: Seq[String], value: String): Option[String]
}

private[registry] class NaiveRegistry extends Registry {
private[this] var registry = Map.empty[Seq[String], String]

def iterator(): Iterator[Entry] = synchronized(registry).iterator.map(Entry.TupledMethod)

def put(key: Seq[String], value: String): Option[String] = {
val sanitizedKey = key.map(sanitize)
val sanitizedValue = sanitize(value)
synchronized {
val result = registry.get(sanitizedKey)
registry += sanitizedKey -> sanitizedValue
result
}
}

private[this] def sanitize(key: String): String =
key.filter { char => char > 31 && char <= 127 && char != '/' }
}

private[registry] object GlobalRegistry {
private[this] val registry = new NaiveRegistry

def get: Registry = registry
}
8 changes: 8 additions & 0 deletions util-registry/src/test/scala/BUILD
@@ -0,0 +1,8 @@
junit_tests(name='scala',
dependencies=[
'3rdparty/jvm/junit',
'3rdparty/jvm/org/scalatest',
'util/util-registry/src/main/scala',
],
sources=rglobs('*.scala')
)
@@ -0,0 +1,10 @@
package com.twitter.registry

import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner

@RunWith(classOf[JUnitRunner])
class NaiveRegistryTest extends RegistryTest {
def mkRegistry(): Registry = new NaiveRegistry()
def name: String = "NaiveRegistry"
}
@@ -0,0 +1,76 @@
package com.twitter.registry

import java.lang.{Character => JCharacter}
import org.scalatest.FunSuite

abstract class RegistryTest extends FunSuite {
def mkRegistry(): Registry
def name: String

test(s"$name can insert a key/value pair and then read it") {
val registry = mkRegistry()
registry.put(Seq("foo"), "bar")
assert(registry.toSet == Set(Entry(Seq("foo"), "bar")))
}

test(s"$name's iterator is not affected by adding an element") {
val registry = mkRegistry()
registry.put(Seq("foo"), "bar")
val iter = registry.iterator()
registry.put(Seq("foo"), "baz")
assert(iter.next() == Entry(Seq("foo"), "bar"))
assert(!iter.hasNext)
}

test(s"$name can overwrite old element") {
val registry = mkRegistry()
registry.put(Seq("foo"), "bar")
registry.put(Seq("foo"), "baz")
assert((registry.toSet) == Set(Entry(Seq("foo"), "baz")))
}

test(s"$name can return the old element when replacing") {
val registry = mkRegistry()
registry.put(Seq("foo"), "bar")
assert(registry.put(Seq("foo"), "baz") == Some("bar"))
assert(registry.toSet == Set(Entry(Seq("foo"), "baz")))
}

test(s"$name can support multiple elements") {
val registry = mkRegistry()
registry.put(Seq("foo"), "bar")
registry.put(Seq("baz"), "qux")
assert(registry.toSet == Set(Entry(Seq("foo"), "bar"), Entry(Seq("baz"), "qux")))
}

test(s"$name can support nontrivial keys") {
val registry = mkRegistry()
registry.put(Seq("foo", "bar", "baz"), "qux")
assert(registry.toSet == Set(Entry(Seq("foo", "bar", "baz"), "qux")))
}

test(s"$name can support empty keys") {
val registry = mkRegistry()
registry.put(Seq(), "qux")
assert(registry.toSet == Set(Entry(Seq(), "qux")))
}

test(s"$name can sanitize bad values") {
val registry = mkRegistry()
registry.put(Seq("foo"), "q/ux")
assert(registry.toSet == Set(Entry(Seq("foo"), "qux")))
}

test(s"$name can sanitize bad keys") {
val registry = mkRegistry()
registry.put(Seq("fo☃o", s"bar${JCharacter.toString(31)}"), "qux")
assert(registry.toSet == Set(Entry(Seq("foo", "bar"), "qux")))
}

test(s"$name can support keys that are subsequences of other keys") {
val registry = mkRegistry()
registry.put(Seq("foo"), "bar")
registry.put(Seq("foo", "baz"), "qux")
assert(registry.toSet == Set(Entry(Seq("foo"), "bar"), Entry(Seq("foo", "baz"), "qux")))
}
}

0 comments on commit 310f6e1

Please sign in to comment.