diff --git a/akka-actor/src/main/scala/akka/AkkaException.scala b/akka-actor/src/main/scala/akka/AkkaException.scala
index 73072b2894..fbeae4b105 100644
--- a/akka-actor/src/main/scala/akka/AkkaException.scala
+++ b/akka-actor/src/main/scala/akka/AkkaException.scala
@@ -23,14 +23,13 @@ import java.net.{InetAddress, UnknownHostException}
import AkkaException._
val exceptionName = getClass.getName
- val uuid = "%s_%s".format(hostname, newUuid)
+ lazy val uuid = "%s_%s".format(hostname, newUuid)
- override val toString = "%s\n\t[%s]\n\t%s\n\t%s".format(exceptionName, uuid, message, stackTrace)
+ override lazy val toString = "%s\n\t[%s]\n\t%s\n\t%s".format(exceptionName, uuid, message, stackTrace)
- val stackTrace = {
+ lazy val stackTrace = {
val sw = new StringWriter
- val pw = new PrintWriter(sw)
- printStackTrace(pw)
+ printStackTrace(new PrintWriter(sw))
sw.toString
}
}
diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala
index 7b2487b9a2..8febe461ff 100644
--- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala
+++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala
@@ -481,6 +481,19 @@ trait ActorRef extends ActorRefShared with java.lang.Comparable[ActorRef] { scal
*/
def getSupervisor(): ActorRef = supervisor getOrElse null
+ /**
+ * Returns an unmodifiable Java Map containing the linked actors,
+ * please note that the backing map is thread-safe but not immutable
+ */
+ def linkedActors: JMap[Uuid, ActorRef]
+
+ /**
+ * Java API
+ * Returns an unmodifiable Java Map containing the linked actors,
+ * please note that the backing map is thread-safe but not immutable
+ */
+ def getLinkedActors(): JMap[Uuid, ActorRef] = linkedActors
+
protected[akka] def invoke(messageHandle: MessageInvocation): Unit
protected[akka] def postMessageToMailbox(message: Any, senderOption: Option[ActorRef]): Unit
@@ -508,8 +521,6 @@ trait ActorRef extends ActorRefShared with java.lang.Comparable[ActorRef] { scal
protected[akka] def registerSupervisorAsRemoteActor: Option[Uuid]
- protected[akka] def linkedActors: JMap[Uuid, ActorRef]
-
override def hashCode: Int = HashCode.hash(HashCode.SEED, uuid)
override def equals(that: Any): Boolean = {
@@ -535,7 +546,7 @@ trait ActorRef extends ActorRefShared with java.lang.Comparable[ActorRef] { scal
}
/**
- * Local (serializable) ActorRef that is used when referencing the Actor on its "home" node.
+ * Local (serializable) ActorRef that is used when referencing the Actor on its "home" node.
*
* @author Jonas Bonér
*/
@@ -679,7 +690,7 @@ class LocalActorRef private[akka] (
def link(actorRef: ActorRef) = guard.withGuard {
if (actorRef.supervisor.isDefined) throw new IllegalActorStateException(
"Actor can only have one supervisor [" + actorRef + "], e.g. link(actor) fails")
- linkedActors.put(actorRef.uuid, actorRef)
+ _linkedActors.put(actorRef.uuid, actorRef)
actorRef.supervisor = Some(this)
}
@@ -689,9 +700,9 @@ class LocalActorRef private[akka] (
* To be invoked from within the actor itself.
*/
def unlink(actorRef: ActorRef) = guard.withGuard {
- if (!linkedActors.containsKey(actorRef.uuid)) throw new IllegalActorStateException(
- "Actor [" + actorRef + "] is not a linked actor, can't unlink")
- linkedActors.remove(actorRef.uuid)
+ if(_linkedActors.remove(actorRef.uuid) eq null)
+ throw new IllegalActorStateException("Actor [" + actorRef + "] is not a linked actor, can't unlink")
+
actorRef.supervisor = None
}
@@ -758,17 +769,6 @@ class LocalActorRef private[akka] (
protected[akka] def mailbox_=(value: AnyRef): AnyRef = { _mailbox = value; value }
- /**
- * Shuts down and removes all linked actors.
- */
- def shutdownLinkedActors() {
- val i = linkedActors.values.iterator
- while(i.hasNext) {
- i.next.stop
- i.remove
- }
- }
-
/**
* Returns the supervisor, if there is one.
*/
@@ -946,8 +946,9 @@ class LocalActorRef private[akka] (
}
protected[akka] def restartLinkedActors(reason: Throwable, maxNrOfRetries: Option[Int], withinTimeRange: Option[Int]) = {
- import scala.collection.JavaConversions._
- linkedActors.values foreach { actorRef =>
+ val i = _linkedActors.values.iterator
+ while(i.hasNext) {
+ val actorRef = i.next
actorRef.lifeCycle match {
// either permanent or none where default is permanent
case Temporary => shutDownTemporaryActor(actorRef)
@@ -965,7 +966,7 @@ class LocalActorRef private[akka] (
} else None
}
- protected[akka] def linkedActors: JMap[Uuid, ActorRef] = _linkedActors
+ def linkedActors: JMap[Uuid, ActorRef] = java.util.Collections.unmodifiableMap(_linkedActors)
// ========= PRIVATE FUNCTIONS =========
@@ -977,11 +978,11 @@ class LocalActorRef private[akka] (
private def shutDownTemporaryActor(temporaryActor: ActorRef) {
temporaryActor.stop
- linkedActors.remove(temporaryActor.uuid) // remove the temporary actor
+ _linkedActors.remove(temporaryActor.uuid) // remove the temporary actor
// if last temporary actor is gone, then unlink me from supervisor
- if (linkedActors.isEmpty) {
+ if (_linkedActors.isEmpty)
notifySupervisorWithMessage(UnlinkAndStop(this))
- }
+
true
}
@@ -1006,7 +1007,15 @@ class LocalActorRef private[akka] (
// FIXME to fix supervisor restart of remote actor for oneway calls, inject a supervisor proxy that can send notification back to client
_supervisor.foreach { sup =>
if (sup.isShutdown) { // if supervisor is shut down, game over for all linked actors
- shutdownLinkedActors
+ //Scoped stop all linked actors, to avoid leaking the 'i' val
+ {
+ val i = _linkedActors.values.iterator
+ while(i.hasNext) {
+ i.next.stop
+ i.remove
+ }
+ }
+ //Stop the actor itself
stop
} else sup ! notification // else notify supervisor
}
@@ -1121,13 +1130,12 @@ private[akka] case class RemoteActorRef private[akka] (
def spawnLink(clazz: Class[_ <: Actor]): ActorRef = unsupported
def spawnLinkRemote(clazz: Class[_ <: Actor], hostname: String, port: Int, timeout: Long): ActorRef = unsupported
def supervisor: Option[ActorRef] = unsupported
- def shutdownLinkedActors: Unit = unsupported
+ def linkedActors: JMap[Uuid, ActorRef] = unsupported
protected[akka] def mailbox: AnyRef = unsupported
protected[akka] def mailbox_=(value: AnyRef): AnyRef = unsupported
protected[akka] def handleTrapExit(dead: ActorRef, reason: Throwable): Unit = unsupported
protected[akka] def restart(reason: Throwable, maxNrOfRetries: Option[Int], withinTimeRange: Option[Int]): Unit = unsupported
protected[akka] def restartLinkedActors(reason: Throwable, maxNrOfRetries: Option[Int], withinTimeRange: Option[Int]): Unit = unsupported
- protected[akka] def linkedActors: JMap[Uuid, ActorRef] = unsupported
protected[akka] def invoke(messageHandle: MessageInvocation): Unit = unsupported
protected[akka] def supervisor_=(sup: Option[ActorRef]): Unit = unsupported
protected[akka] def actorInstance: AtomicReference[Actor] = unsupported
@@ -1149,11 +1157,6 @@ trait ActorRefShared {
* Returns the uuid for the actor.
*/
def uuid: Uuid
-
- /**
- * Shuts down and removes all linked actors.
- */
- def shutdownLinkedActors(): Unit
}
/**
diff --git a/akka-actor/src/main/scala/akka/actor/Supervisor.scala b/akka-actor/src/main/scala/akka/actor/Supervisor.scala
index d9e77dcbcb..bb08bcdf80 100644
--- a/akka-actor/src/main/scala/akka/actor/Supervisor.scala
+++ b/akka-actor/src/main/scala/akka/actor/Supervisor.scala
@@ -157,10 +157,16 @@ sealed class Supervisor(handler: FaultHandlingStrategy) {
* @author Jonas Bonér
*/
final class SupervisorActor private[akka] (handler: FaultHandlingStrategy) extends Actor {
- import self._
- faultHandler = handler
+ self.faultHandler = handler
- override def postStop(): Unit = shutdownLinkedActors
+ override def postStop(): Unit = {
+ val i = self.linkedActors.values.iterator
+ while(i.hasNext) {
+ val ref = i.next
+ ref.stop
+ self.unlink(ref)
+ }
+ }
def receive = {
// FIXME add a way to respond to MaximumNumberOfRestartsWithinTimeRangeReached in declaratively configured Supervisor
diff --git a/akka-actor/src/main/scala/akka/config/Attributes.scala b/akka-actor/src/main/scala/akka/config/Attributes.scala
new file mode 100644
index 0000000000..b8936ede57
--- /dev/null
+++ b/akka-actor/src/main/scala/akka/config/Attributes.scala
@@ -0,0 +1,346 @@
+/**
+ * Copyright (C) 2009-2011 Scalable Solutions AB
+ *
+ * Based on Configgy by Robey Pointer.
+ * Copyright 2009 Robey Pointer
+ * http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+package akka.config
+
+import java.util.regex.Pattern
+import scala.collection.{immutable, mutable, Map}
+import scala.util.Sorting
+import akka.config.string._
+
+
+private[config] abstract class Cell
+private[config] case class StringCell(value: String) extends Cell
+private[config] case class AttributesCell(attr: Attributes) extends Cell
+private[config] case class StringListCell(array: Array[String]) extends Cell
+
+
+/**
+ * Actual implementation of ConfigMap.
+ * Stores items in Cell objects, and handles interpolation and key recursion.
+ */
+private[config] class Attributes(val config: Configuration, val name: String) extends ConfigMap {
+
+ private val cells = new mutable.HashMap[String, Cell]
+ var inheritFrom: Option[ConfigMap] = None
+
+ def this(config: Configuration, name: String, copyFrom: ConfigMap) = {
+ this(config, name)
+ copyFrom.copyInto(this)
+ }
+
+ def keys: Iterator[String] = cells.keysIterator
+
+ def getName() = name
+
+ override def toString() = {
+ val buffer = new StringBuilder("{")
+ buffer ++= name
+ buffer ++= (inheritFrom match {
+ case Some(a: Attributes) => " (inherit=" + a.name + ")"
+ case None => ""
+ })
+ buffer ++= ": "
+ for (key <- sortedKeys) {
+ buffer ++= key
+ buffer ++= "="
+ buffer ++= (cells(key) match {
+ case StringCell(x) => "\"" + x.quoteC + "\""
+ case AttributesCell(x) => x.toString
+ case StringListCell(x) => x.mkString("[", ",", "]")
+ })
+ buffer ++= " "
+ }
+ buffer ++= "}"
+ buffer.toString
+ }
+
+ override def equals(obj: Any) = {
+ if (! obj.isInstanceOf[Attributes]) {
+ false
+ } else {
+ val other = obj.asInstanceOf[Attributes]
+ (other.sortedKeys.toList == sortedKeys.toList) &&
+ (cells.keys forall (k => { cells(k) == other.cells(k) }))
+ }
+ }
+
+ /**
+ * Look up a value cell for a given key. If the key is compound (ie,
+ * "abc.xyz"), look up the first segment, and if it refers to an inner
+ * Attributes object, recursively look up that cell. If it's not an
+ * Attributes or it doesn't exist, return None. For a non-compound key,
+ * return the cell if it exists, or None if it doesn't.
+ */
+ private def lookupCell(key: String): Option[Cell] = {
+ val elems = key.split("\\.", 2)
+ if (elems.length > 1) {
+ cells.get(elems(0)) match {
+ case Some(AttributesCell(x)) => x.lookupCell(elems(1))
+ case None => inheritFrom match {
+ case Some(a: Attributes) =>
+ a.lookupCell(key)
+ case _ => None
+ }
+ case _ => None
+ }
+ } else {
+ cells.get(elems(0)) match {
+ case x @ Some(_) => x
+ case None => inheritFrom match {
+ case Some(a: Attributes) => a.lookupCell(key)
+ case _ => None
+ }
+ }
+ }
+ }
+
+ /**
+ * Determine if a key is compound (and requires recursion), and if so,
+ * return the nested Attributes block and simple key that can be used to
+ * make a recursive call. If the key is simple, return None.
+ *
+ * If the key is compound, but nested Attributes objects don't exist
+ * that match the key, an attempt will be made to create the nested
+ * Attributes objects. If one of the key segments already refers to an
+ * attribute that isn't a nested Attribute object, a ConfigException
+ * will be thrown.
+ *
+ * For example, for the key "a.b.c", the Attributes object for "a.b"
+ * and the key "c" will be returned, creating the "a.b" Attributes object
+ * if necessary. If "a" or "a.b" exists but isn't a nested Attributes
+ * object, then an ConfigException will be thrown.
+ */
+ @throws(classOf[ConfigException])
+ private def recurse(key: String): Option[(Attributes, String)] = {
+ val elems = key.split("\\.", 2)
+ if (elems.length > 1) {
+ val attr = (cells.get(elems(0)) match {
+ case Some(AttributesCell(x)) => x
+ case Some(_) => throw new ConfigException("Illegal key " + key)
+ case None => createNested(elems(0))
+ })
+ attr.recurse(elems(1)) match {
+ case ret @ Some((a, b)) => ret
+ case None => Some((attr, elems(1)))
+ }
+ } else {
+ None
+ }
+ }
+
+ def replaceWith(newAttributes: Attributes): Unit = {
+ // stash away subnodes and reinsert them.
+ val subnodes = for ((key, cell @ AttributesCell(_)) <- cells.toList) yield (key, cell)
+ cells.clear
+ cells ++= newAttributes.cells
+ for ((key, cell) <- subnodes) {
+ newAttributes.cells.get(key) match {
+ case Some(AttributesCell(newattr)) =>
+ cell.attr.replaceWith(newattr)
+ cells(key) = cell
+ case None =>
+ cell.attr.replaceWith(new Attributes(config, ""))
+ }
+ }
+ }
+
+ private def createNested(key: String): Attributes = {
+ val attr = new Attributes(config, if (name.equals("")) key else (name + "." + key))
+ cells(key) = new AttributesCell(attr)
+ attr
+ }
+
+ def getString(key: String): Option[String] = {
+ lookupCell(key) match {
+ case Some(StringCell(x)) => Some(x)
+ case Some(StringListCell(x)) => Some(x.toList.mkString("[", ",", "]"))
+ case _ => None
+ }
+ }
+
+ def getConfigMap(key: String): Option[ConfigMap] = {
+ lookupCell(key) match {
+ case Some(AttributesCell(x)) => Some(x)
+ case _ => None
+ }
+ }
+
+ def configMap(key: String): ConfigMap = makeAttributes(key, true)
+
+ private[config] def makeAttributes(key: String): Attributes = makeAttributes(key, false)
+
+ private[config] def makeAttributes(key: String, withInherit: Boolean): Attributes = {
+ if (key == "") {
+ return this
+ }
+ recurse(key) match {
+ case Some((attr, name)) =>
+ attr.makeAttributes(name, withInherit)
+ case None =>
+ val cell = if (withInherit) lookupCell(key) else cells.get(key)
+ cell match {
+ case Some(AttributesCell(x)) => x
+ case Some(_) => throw new ConfigException("Illegal key " + key)
+ case None => createNested(key)
+ }
+ }
+ }
+
+ def getList(key: String): Seq[String] = {
+ lookupCell(key) match {
+ case Some(StringListCell(x)) => x
+ case Some(StringCell(x)) => Array[String](x)
+ case _ => Array[String]()
+ }
+ }
+
+ def setString(key: String, value: String): Unit = {
+ recurse(key) match {
+ case Some((attr, name)) => attr.setString(name, value)
+ case None => cells.get(key) match {
+ case Some(AttributesCell(_)) => throw new ConfigException("Illegal key " + key)
+ case _ => cells.put(key, new StringCell(value))
+ }
+ }
+ }
+
+ def setList(key: String, value: Seq[String]): Unit = {
+ recurse(key) match {
+ case Some((attr, name)) => attr.setList(name, value)
+ case None => cells.get(key) match {
+ case Some(AttributesCell(_)) => throw new ConfigException("Illegal key " + key)
+ case _ => cells.put(key, new StringListCell(value.toArray))
+ }
+ }
+ }
+
+ def setConfigMap(key: String, value: ConfigMap): Unit = {
+ recurse(key) match {
+ case Some((attr, name)) => attr.setConfigMap(name, value)
+ case None =>
+ val subName = if (name == "") key else (name + "." + key)
+ cells.get(key) match {
+ case Some(AttributesCell(_)) =>
+ cells.put(key, new AttributesCell(new Attributes(config, subName, value)))
+ case None =>
+ cells.put(key, new AttributesCell(new Attributes(config, subName, value)))
+ case _ =>
+ throw new ConfigException("Illegal key " + key)
+ }
+ }
+ }
+
+ def contains(key: String): Boolean = {
+ recurse(key) match {
+ case Some((attr, name)) => attr.contains(name)
+ case None => cells.contains(key)
+ }
+ }
+
+ def remove(key: String): Boolean = {
+ recurse(key) match {
+ case Some((attr, name)) => attr.remove(name)
+ case None => {
+ cells.removeKey(key) match {
+ case Some(_) => true
+ case None => false
+ }
+ }
+ }
+ }
+
+ def asMap: Map[String, String] = {
+ var ret = immutable.Map.empty[String, String]
+ for ((key, value) <- cells) {
+ value match {
+ case StringCell(x) => ret = ret.update(key, x)
+ case StringListCell(x) => ret = ret.update(key, x.mkString("[", ",", "]"))
+ case AttributesCell(x) =>
+ for ((k, v) <- x.asMap) {
+ ret = ret.update(key + "." + k, v)
+ }
+ }
+ }
+ ret
+ }
+
+ def toConfigString: String = {
+ toConfigList().mkString("", "\n", "\n")
+ }
+
+ private def toConfigList(): List[String] = {
+ val buffer = new mutable.ListBuffer[String]
+ for (key <- Sorting.stableSort(cells.keys.toList)) {
+ cells(key) match {
+ case StringCell(x) =>
+ buffer += (key + " = \"" + x.quoteC + "\"")
+ case StringListCell(x) =>
+ buffer += (key + " = [")
+ buffer ++= x.map { " \"" + _.quoteC + "\"," }
+ buffer += "]"
+ case AttributesCell(node) =>
+ buffer += (key + node.inheritFrom.map { " (inherit=\"" + _.asInstanceOf[Attributes].name + "\")" }.getOrElse("") + " {")
+ buffer ++= node.toConfigList().map { " " + _ }
+ buffer += "}"
+ }
+ }
+ buffer.toList
+ }
+
+ // substitute "$(...)" strings with looked-up vars
+ // (and find "\$" and replace them with "$")
+ private val INTERPOLATE_RE = """(? ""
+ case attr :: xs => attr.getString(key) match {
+ case Some(x) => x
+ case None => lookup(key, xs)
+ }
+ }
+ }
+
+ s.regexSub(INTERPOLATE_RE) { m =>
+ if (m.matched == "\\$") {
+ "$"
+ } else {
+ lookup(m.group(1), List(this, root, EnvironmentAttributes))
+ }
+ }
+ }
+
+ protected[config] def interpolate(key: String, s: String): String = {
+ recurse(key) match {
+ case Some((attr, name)) => attr.interpolate(this, s)
+ case None => interpolate(this, s)
+ }
+ }
+
+ // make a deep copy of the Attributes tree.
+ def copy(): Attributes = {
+ copyInto(new Attributes(config, name))
+ }
+
+ def copyInto[T <: ConfigMap](attr: T): T = {
+ inheritFrom match {
+ case Some(a: Attributes) => a.copyInto(attr)
+ case _ =>
+ }
+ for ((key, value) <- cells.elements) {
+ value match {
+ case StringCell(x) => attr(key) = x
+ case StringListCell(x) => attr(key) = x
+ case AttributesCell(x) => attr.setConfigMap(key, x.copy())
+ }
+ }
+ attr
+ }
+}
diff --git a/akka-actor/src/main/scala/akka/config/Config.scala b/akka-actor/src/main/scala/akka/config/Config.scala
index 1be08b14ae..59c8417b69 100644
--- a/akka-actor/src/main/scala/akka/config/Config.scala
+++ b/akka-actor/src/main/scala/akka/config/Config.scala
@@ -5,8 +5,7 @@
package akka.config
import akka.AkkaException
-import akka.actor.{EventHandler}
-import net.lag.configgy.{Config => CConfig, Configgy, ParseException}
+import akka.actor.EventHandler
import java.net.InetSocketAddress
import java.lang.reflect.Method
@@ -56,40 +55,39 @@ object Config {
if (System.getProperty("akka.config", "") != "") {
val configFile = System.getProperty("akka.config", "")
try {
- Configgy.configure(configFile)
+ Configure.configure(configFile)
println("Config loaded from -Dakka.config=" + configFile)
} catch {
- case cause: ParseException =>
+ case cause: ParseException =>
val e = new ConfigurationException(
"Config could not be loaded from -Dakka.config=" + configFile +
"\n\tdue to: " + cause.toString)
EventHandler notifyListeners EventHandler.Error(e, this)
throw e
-
}
- Configgy.config
+ Configure.config
} else if (getClass.getClassLoader.getResource(confName) ne null) {
try {
- Configgy.configureFromResource(confName, getClass.getClassLoader)
+ Configure.configureFromResource(confName, getClass.getClassLoader)
println("Config [" + confName + "] loaded from the application classpath.")
} catch {
- case cause: ParseException =>
+ case cause: ParseException =>
val e = new ConfigurationException(
"Can't load '" + confName + "' config file from application classpath," +
"\n\tdue to: " + cause.toString)
EventHandler notifyListeners EventHandler.Error(e, this)
throw e
}
- Configgy.config
+ Configure.config
} else if (HOME.isDefined) {
try {
val configFile = HOME.get + "/config/" + confName
- Configgy.configure(configFile)
+ Configure.configure(configFile)
println(
- "AKKA_HOME is defined as [" + HOME.getOrElse(throwNoAkkaHomeException) +
+ "AKKA_HOME is defined as [" + HOME.getOrElse(throwNoAkkaHomeException) +
"], config loaded from [" + configFile + "].")
} catch {
- case cause: ParseException =>
+ case cause: ParseException =>
val e = throw new ConfigurationException(
"AKKA_HOME is defined as [" + HOME.get + "] " +
"\n\tbut the 'akka.conf' config file can not be found at [" + HOME.get + "/config/"+ confName + "]," +
@@ -97,7 +95,7 @@ object Config {
EventHandler notifyListeners EventHandler.Error(e, this)
throw e
}
- Configgy.config
+ Configure.config
} else {
println(
"\nCan't load '" + confName + "'." +
@@ -107,10 +105,9 @@ object Config {
"\n\t3. Define 'AKKA_HOME' environment variable pointing to the root of the Akka distribution." +
"\nI have no way of finding the '" + confName + "' configuration file." +
"\nUsing default values everywhere.")
- CConfig.fromString("") // default empty config
+ Configuration.fromString("") // default empty config
}
}
- if (config.getBool("akka.enable-jmx", true)) config.registerWithJmx("akka")
val CONFIG_VERSION = config.getString("akka.version", VERSION)
if (VERSION != CONFIG_VERSION) throw new ConfigurationException(
diff --git a/akka-actor/src/main/scala/akka/config/ConfigMap.scala b/akka-actor/src/main/scala/akka/config/ConfigMap.scala
new file mode 100644
index 0000000000..0e210b9b9f
--- /dev/null
+++ b/akka-actor/src/main/scala/akka/config/ConfigMap.scala
@@ -0,0 +1,369 @@
+/**
+ * Copyright (C) 2009-2011 Scalable Solutions AB
+ *
+ * Based on Configgy by Robey Pointer.
+ * Copyright 2009 Robey Pointer
+ * http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+package akka.config
+
+import scala.collection.Map
+import scala.util.Sorting
+
+
+class ConfigException(reason: String) extends Exception(reason)
+
+
+/**
+ * Abstract trait for a map of string keys to strings, string lists, or (nested) ConfigMaps.
+ * Integers and booleans may also be stored and retrieved, but they are converted to/from
+ * strings in the process.
+ */
+trait ConfigMap {
+ private val TRUE = "true"
+ private val FALSE = "false"
+
+
+ // ----- required methods
+
+ /**
+ * Lookup an entry in this map, and if it exists and can be represented
+ * as a string, return it. Strings will be returned as-is, and string
+ * lists will be returned as a combined string. Nested AttributeMaps
+ * will return None as if there was no entry present.
+ */
+ def getString(key: String): Option[String]
+
+ /**
+ * Lookup an entry in this map, and if it exists and is a nested
+ * ConfigMap, return it. If the entry is a string or string list,
+ * it will return None as if there was no entry present.
+ */
+ def getConfigMap(key: String): Option[ConfigMap]
+
+ /**
+ * Lookup an entry in this map, and if it exists and is a nested
+ * ConfigMap, return it. If not, create an empty map with this name
+ * and return that.
+ *
+ * @throws ConfigException if the key already refers to a string or
+ * string list
+ */
+ def configMap(key: String): ConfigMap
+
+ /**
+ * Lookup an entry in this map, and if it exists and can be represented
+ * as a string list, return it. String lists will be returned as-is, and
+ * strings will be returned as an array of length 1. If the entry doesn't
+ * exist or is a nested ConfigMap, an empty sequence is returned.
+ */
+ def getList(key: String): Seq[String]
+
+ /**
+ * Set a key/value pair in this map. If an entry already existed with
+ * that key, it's replaced.
+ *
+ * @throws ConfigException if the key already refers to a nested
+ * ConfigMap
+ */
+ def setString(key: String, value: String): Unit
+
+ /**
+ * Set a key/value pair in this map. If an entry already existed with
+ * that key, it's replaced.
+ *
+ * @throws ConfigException if the key already refers to a nested
+ * ConfigMap
+ */
+ def setList(key: String, value: Seq[String]): Unit
+
+ /**
+ * Put a nested ConfigMap inside this one. If an entry already existed with
+ * that key, it's replaced. The ConfigMap is deep-copied at insert-time.
+ *
+ * @throws ConfigException if the key already refers to a value that isn't
+ * a nested ConfigMap
+ */
+ def setConfigMap(key: String, value: ConfigMap): Unit
+
+ /**
+ * Returns true if this map contains the given key.
+ */
+ def contains(key: String): Boolean
+
+ /**
+ * Remove an entry with the given key, if it exists. Returns true if
+ * an entry was actually removed, false if not.
+ */
+ def remove(key: String): Boolean
+
+ /**
+ * Return an iterator across the keys of this map.
+ */
+ def keys: Iterator[String]
+
+ /**
+ * Return a new (immutable) map containing a deep copy of all the keys
+ * and values from this AttributeMap. Keys from nested maps will be
+ * compound (like `"inner.name"`).
+ */
+ def asMap(): Map[String, String]
+
+ /**
+ * Make a deep copy of this ConfigMap. Any inheritance chains will be
+ * deep-copied, but the inheritance will not be preserved: the copied
+ * ConfigMap stands alone as its own set of objects, reflecting the
+ * frozen state of any inherited ConfigMaps.
+ */
+ def copy(): ConfigMap
+
+ /**
+ * Make this ConfigMap inherit default values from another ConfigMap.
+ * Any attributes that aren't explicitly set will fall back to the inherited
+ * ConfigMap on lookup.
+ */
+ def inheritFrom_=(config: Option[ConfigMap]): Unit
+
+ /**
+ * Return any ConfigMap that is used as a fall back on lookups.
+ */
+ def inheritFrom: Option[ConfigMap]
+
+
+ // ----- convenience methods
+
+ /**
+ * If the requested key is present, return its value. Otherwise, return
+ * the given default value.
+ */
+ def getString(key: String, defaultValue: String): String = {
+ getString(key) match {
+ case Some(x) => x
+ case None => defaultValue
+ }
+ }
+
+ /**
+ * If the requested key is present and can be converted into an int
+ * (via `String.toInt`), return that int. Otherwise, return `None`.
+ */
+ def getInt(key: String): Option[Int] = {
+ getString(key) match {
+ case Some(x) => {
+ try {
+ Some(x.toInt)
+ } catch {
+ case _: NumberFormatException => None
+ }
+ }
+ case None => None
+ }
+ }
+
+ /**
+ * If the requested key is present and can be converted into an int
+ * (via `String.toInt`), return that int. Otherwise,
+ * return the given default value.
+ */
+ def getInt(key: String, defaultValue: Int): Int = {
+ getInt(key) match {
+ case Some(n) => n
+ case None => defaultValue
+ }
+ }
+
+ /**
+ * If the requested key is present and can be converted into a long
+ * (via `String.toLong`), return that long. Otherwise, return `None`.
+ */
+ def getLong(key: String): Option[Long] = {
+ getString(key) match {
+ case Some(x) => {
+ try {
+ Some(x.toLong)
+ } catch {
+ case _: NumberFormatException => None
+ }
+ }
+ case None => None
+ }
+ }
+
+ /**
+ * If the requested key is present and can be converted into a long
+ * (via `String.toLong`), return that long. Otherwise,
+ * return the given default value.
+ */
+ def getLong(key: String, defaultValue: Long): Long = {
+ getLong(key) match {
+ case Some(n) => n
+ case None => defaultValue
+ }
+ }
+
+ /**
+ * If the requested key is present and can be converted into a double
+ * (via `String.toDouble`), return that double. Otherwise, return `None`.
+ */
+ def getDouble(key: String): Option[Double] = {
+ getString(key) match {
+ case Some(x) => {
+ try {
+ Some(x.toDouble)
+ } catch {
+ case _: NumberFormatException => None
+ }
+ }
+ case None => None
+ }
+ }
+
+ /**
+ * If the requested key is present and can be converted into a double
+ * (via `String.toDouble`), return that double. Otherwise,
+ * return the given default value.
+ */
+ def getDouble(key: String, defaultValue: Double): Double = {
+ getDouble(key) match {
+ case Some(n) => n
+ case None => defaultValue
+ }
+ }
+
+ /**
+ * If the requested key is present and can be converted into a bool
+ * (by being either `"true"` or `"false"`),
+ * return that bool. Otherwise, return `None`.
+ */
+ def getBool(key: String): Option[Boolean] = {
+ getString(key) match {
+ case Some(x) =>
+ if (x != TRUE && x != FALSE) throw new ConfigException("invalid boolean value")
+ Some(x.equals(TRUE))
+ case None => None
+ }
+ }
+
+ /**
+ * If the requested key is present and can be converted into a bool
+ * (by being either `"true"` or `"false"`),
+ * return that bool. Otherwise, return the given default value.
+ */
+ def getBool(key: String, defaultValue: Boolean): Boolean = {
+ getBool(key) match {
+ case Some(b) => b
+ case None => defaultValue
+ }
+ }
+
+ /**
+ * Set the given key to an int value, by converting it to a string
+ * first.
+ */
+ def setInt(key: String, value: Int): Unit = setString(key, value.toString)
+
+ /**
+ * Set the given key to a long value, by converting it to a string
+ * first.
+ */
+ def setLong(key: String, value: Long): Unit = setString(key, value.toString)
+
+ /**
+ * Set the given key to a double value, by converting it to a string
+ * first.
+ */
+ def setDouble(key: String, value: Double): Unit = setString(key, value.toString)
+
+ /**
+ * Set the given key to a bool value, by converting it to a string
+ * first.
+ */
+ def setBool(key: String, value: Boolean): Unit = {
+ setString(key, if (value) TRUE else FALSE)
+ }
+
+ /**
+ * Return the keys of this map, in sorted order.
+ */
+ def sortedKeys() = {
+ // :( why does this have to be done manually?
+ val keys = this.keys.toList.toArray
+ Sorting.quickSort(keys)
+ keys
+ }
+
+ /**
+ * Convert this ConfigMap into a string which could be written into a config file.
+ */
+ def toConfigString: String
+
+ def copyInto[T <: ConfigMap](configMap: T): T
+
+ def copyInto(obj: AnyRef) {
+ val cls = obj.getClass
+ //val log = Logger.get(cls)
+ val methods = cls.getMethods().filter { method =>
+ method.getName().endsWith("_$eq") && method.getParameterTypes().size == 1
+ }.toList
+ keys.foreach { key =>
+ val setters = methods.filter { _.getName() == key + "_$eq" }
+/* if (setters.size == 0) {
+ log.warning("Ignoring config key '%s' which doesn't have a setter in class %s", key, cls)
+ }*/
+ setters.foreach { method =>
+ val expectedType = method.getParameterTypes().first.getCanonicalName
+ val param = expectedType match {
+ case "int" => getInt(key)
+ case "long" => getLong(key)
+ case "float" => getDouble(key).map { _.toFloat }
+ case "double" => getDouble(key)
+ case "boolean" => getBool(key)
+ case "java.lang.String" => getString(key)
+ case _ => None // ignore for now
+ }
+ param.map { p => method.invoke(obj, p.asInstanceOf[Object]) }
+ }
+ }
+ }
+
+ /**
+ * If the requested key is present, return its value as a string. Otherwise, throw a
+ * ConfigException. `toInt` and `toBoolean` may be called on the
+ * returned string if an int or bool is desired.
+ */
+ def apply(key: String): String = getString(key) match {
+ case None => throw new ConfigException("undefined config: " + key)
+ case Some(v) => v
+ }
+
+ /** Equivalent to `getString(key, defaultValue)`. */
+ def apply(key: String, defaultValue: String) = getString(key, defaultValue)
+
+ /** Equivalent to `getInt(key, defaultValue)`. */
+ def apply(key: String, defaultValue: Int) = getInt(key, defaultValue)
+
+ /** Equivalent to `getLong(key, defaultValue)`. */
+ def apply(key: String, defaultValue: Long) = getLong(key, defaultValue)
+
+ /** Equivalent to `getBool(key, defaultValue)`. */
+ def apply(key: String, defaultValue: Boolean) = getBool(key, defaultValue)
+
+ /** Equivalent to `setString(key, value)`. */
+ def update(key: String, value: String) = setString(key, value)
+
+ /** Equivalent to `setInt(key, value)`. */
+ def update(key: String, value: Int) = setInt(key, value)
+
+ /** Equivalent to `setLong(key, value)`. */
+ def update(key: String, value: Long) = setLong(key, value)
+
+ /** Equivalent to `setBool(key, value)`. */
+ def update(key: String, value: Boolean) = setBool(key, value)
+
+ /** Equivalent to `setList(key, value)`. */
+ def update(key: String, value: Seq[String]) = setList(key, value)
+
+ /** Get the name of the current config map. */
+ def getName(): String
+}
diff --git a/akka-actor/src/main/scala/akka/config/ConfigParser.scala b/akka-actor/src/main/scala/akka/config/ConfigParser.scala
new file mode 100644
index 0000000000..7d8485897a
--- /dev/null
+++ b/akka-actor/src/main/scala/akka/config/ConfigParser.scala
@@ -0,0 +1,124 @@
+/**
+ * Copyright (C) 2009-2011 Scalable Solutions AB
+ *
+ * Based on Configgy by Robey Pointer.
+ * Copyright 2009 Robey Pointer
+ * http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+package akka.config
+
+import scala.collection.mutable.Stack
+import scala.util.parsing.combinator._
+import scala.util.parsing.input.CharSequenceReader
+import akka.config.string._
+
+
+/**
+ * An exception thrown when parsing a config file, if there was an error
+ * during parsing. The `reason` string will contain the parsing
+ * error details.
+ */
+class ParseException(reason: String, cause: Throwable) extends Exception(reason, cause) {
+ def this(reason: String) = this(reason, null)
+ def this(cause: Throwable) = this(null, cause)
+}
+
+
+private[config] class ConfigParser(var attr: Attributes, val importer: Importer) extends RegexParsers {
+
+ val sections = new Stack[String]
+ var prefix = ""
+
+ // Stack reversed iteration order from 2.7 to 2.8!!
+ def sectionsString = sections.toList.reverse.mkString(".")
+
+ // tokens
+ override val whiteSpace = """(\s+|#[^\n]*\n)+""".r
+ val numberToken: Parser[String] = """-?\d+(\.\d+)?""".r
+ val stringToken: Parser[String] = ("\"" + """([^\\\"]|\\[^ux]|\\\n|\\u[0-9a-fA-F]{4}|\\x[0-9a-fA-F]{2})*""" + "\"").r
+ val identToken: Parser[String] = """([\da-zA-Z_][-\w]*)(\.[a-zA-Z_][-\w]*)*""".r
+ val assignToken: Parser[String] = """=|\?=""".r
+ val tagNameToken: Parser[String] = """[a-zA-Z][-\w]*""".r
+
+
+ def root = rep(includeFile | includeOptFile | assignment | toggle | sectionOpen | sectionClose |
+ sectionOpenBrace | sectionCloseBrace)
+
+ def includeFile = "include" ~> string ^^ {
+ case filename: String =>
+ new ConfigParser(attr.makeAttributes(sectionsString), importer) parse importer.importFile(filename)
+ }
+
+ def includeOptFile = "include?" ~> string ^^ {
+ case filename: String =>
+ new ConfigParser(attr.makeAttributes(sections.mkString(".")), importer) parse importer.importFile(filename, false)
+ }
+
+ def assignment = identToken ~ assignToken ~ value ^^ {
+ case k ~ a ~ v => if (a match {
+ case "=" => true
+ case "?=" => ! attr.contains(prefix + k)
+ }) v match {
+ case x: Long => attr(prefix + k) = x
+ case x: String => attr(prefix + k) = x
+ case x: Array[String] => attr(prefix + k) = x
+ case x: Boolean => attr(prefix + k) = x
+ }
+ }
+
+ def toggle = identToken ~ trueFalse ^^ { case k ~ v => attr(prefix + k) = v }
+
+ def sectionOpen = "<" ~> tagNameToken ~ rep(tagAttribute) <~ ">" ^^ {
+ case name ~ attrList => openBlock(name, attrList)
+ }
+ def tagAttribute = opt(whiteSpace) ~> (tagNameToken <~ "=") ~ string ^^ { case k ~ v => (k, v) }
+ def sectionClose = "" ~> tagNameToken <~ ">" ^^ { name => closeBlock(Some(name)) }
+
+ def sectionOpenBrace = tagNameToken ~ opt("(" ~> rep(tagAttribute) <~ ")") <~ "{" ^^ {
+ case name ~ attrListOption => openBlock(name, attrListOption.getOrElse(Nil))
+ }
+ def sectionCloseBrace = "}" ^^ { x => closeBlock(None) }
+
+ private def openBlock(name: String, attrList: List[(String, String)]) = {
+ val parent = if (sections.size > 0) attr.makeAttributes(sectionsString) else attr
+ sections push name
+ prefix = sectionsString + "."
+ val newBlock = attr.makeAttributes(sectionsString)
+ for ((k, v) <- attrList) k match {
+ case "inherit" =>
+ newBlock.inheritFrom = Some(if (parent.getConfigMap(v).isDefined) parent.makeAttributes(v) else attr.makeAttributes(v))
+ case _ =>
+ throw new ParseException("Unknown block modifier")
+ }
+ }
+
+ private def closeBlock(name: Option[String]) = {
+ if (sections.isEmpty) {
+ failure("dangling close tag")
+ } else {
+ val last = sections.pop
+ if (name.isDefined && last != name.get) {
+ failure("got closing tag for " + name.get + ", expected " + last)
+ } else {
+ prefix = if (sections.isEmpty) "" else sectionsString + "."
+ }
+ }
+ }
+
+
+ def value: Parser[Any] = number | string | stringList | trueFalse
+ def number = numberToken ^^ { x => if (x.contains('.')) x else x.toLong }
+ def string = stringToken ^^ { s => attr.interpolate(prefix, s.substring(1, s.length - 1).unquoteC) }
+ def stringList = "[" ~> repsep(string | numberToken, opt(",")) <~ (opt(",") ~ "]") ^^ { list => list.toArray }
+ def trueFalse: Parser[Boolean] = ("(true|on)".r ^^ { x => true }) | ("(false|off)".r ^^ { x => false })
+
+
+ def parse(in: String): Unit = {
+ parseAll(root, in) match {
+ case Success(result, _) => result
+ case x @ Failure(msg, z) => throw new ParseException(x.toString)
+ case x @ Error(msg, _) => throw new ParseException(x.toString)
+ }
+ }
+}
diff --git a/akka-actor/src/main/scala/akka/config/Configuration.scala b/akka-actor/src/main/scala/akka/config/Configuration.scala
index d6141ac352..6ab17e2d9d 100644
--- a/akka-actor/src/main/scala/akka/config/Configuration.scala
+++ b/akka-actor/src/main/scala/akka/config/Configuration.scala
@@ -1,60 +1,156 @@
/**
* Copyright (C) 2009-2011 Scalable Solutions AB
+ *
+ * Based on Configgy by Robey Pointer.
+ * Copyright 2009 Robey Pointer
+ * http://www.apache.org/licenses/LICENSE-2.0
*/
package akka.config
-/*
-import akka.kernel.{TypedActor, TypedActorProxy}
-import com.google.inject.{AbstractModule}
-import java.util.{List => JList, ArrayList}
-import scala.reflect.BeanProperty
+import java.io.File
+import scala.collection.{Map, Set}
+import scala.collection.{immutable, mutable}
-// ============================================
-// Java version of the configuration API
+class Configuration extends ConfigMap {
+ private var root = new Attributes(this, "")
+ private var reloadAction: Option[() => Unit] = None
+
+ /**
+ * Importer for resolving "include" lines when loading config files.
+ * By default, it's a FilesystemImporter based on the current working
+ * directory.
+ */
+ var importer: Importer = new FilesystemImporter(new File(".").getCanonicalPath)
+ /**
+ * Read config data from a string and use it to populate this object.
+ */
+ def load(data: String) {
+ reloadAction = Some(() => configure(data))
+ reload()
+ }
-sealed abstract class Configuration
+ /**
+ * Read config data from a file and use it to populate this object.
+ */
+ def loadFile(filename: String) {
+ reloadAction = Some(() => configure(importer.importFile(filename)))
+ reload()
+ }
-class RestartStrategy(@BeanProperty val scheme: FailOverScheme, @BeanProperty val maxNrOfRetries: Int, @BeanProperty val withinTimeRange: Int) extends Configuration {
- def transform = akka.kernel.RestartStrategy(scheme.transform, maxNrOfRetries, withinTimeRange)
-}
-class LifeCycle(@BeanProperty val scope: Scope, @BeanProperty val shutdownTime: Int) extends Configuration {
- def transform = akka.kernel.LifeCycle(scope.transform, shutdownTime)
+ /**
+ * Read config data from a file and use it to populate this object.
+ */
+ def loadFile(path: String, filename: String) {
+ importer = new FilesystemImporter(path)
+ loadFile(filename)
+ }
+
+ /**
+ * Reloads the configuration from whatever source it was previously loaded
+ * from, undoing any in-memory changes. This is a no-op if the configuration
+ * data has not be loaded from a source (file or string).
+ */
+ def reload() {
+ reloadAction.foreach(_())
+ }
+
+ private def configure(data: String) {
+ val newRoot = new Attributes(this, "")
+ new ConfigParser(newRoot, importer) parse data
+ root.replaceWith(newRoot)
+ }
+
+ override def toString = root.toString
+
+ // ----- implement AttributeMap by wrapping our root object:
+
+ def getString(key: String): Option[String] = root.getString(key)
+ def getConfigMap(key: String): Option[ConfigMap] = root.getConfigMap(key)
+ def configMap(key: String): ConfigMap = root.configMap(key)
+ def getList(key: String): Seq[String] = root.getList(key)
+ def setString(key: String, value: String): Unit = root.setString(key, value)
+ def setList(key: String, value: Seq[String]): Unit = root.setList(key, value)
+ def setConfigMap(key: String, value: ConfigMap): Unit = root.setConfigMap(key, value)
+ def contains(key: String): Boolean = root.contains(key)
+ def remove(key: String): Boolean = root.remove(key)
+ def keys: Iterator[String] = root.keys
+ def asMap(): Map[String, String] = root.asMap()
+ def toConfigString = root.toConfigString
+ def copy(): ConfigMap = root.copy()
+ def copyInto[T <: ConfigMap](m: T): T = root.copyInto(m)
+ def inheritFrom = root.inheritFrom
+ def inheritFrom_=(config: Option[ConfigMap]) = root.inheritFrom=(config)
+ def getName(): String = root.name
}
-abstract class Scope extends Configuration {
- def transform: akka.kernel.Scope
-}
-class Permanent extends Scope {
- override def transform = akka.kernel.Permanent
-}
-class Transient extends Scope {
- override def transform = akka.kernel.Transient
-}
-class Temporary extends Scope {
- override def transform = akka.kernel.Temporary
-}
-abstract class FailOverScheme extends Configuration {
- def transform: akka.kernel.FailOverScheme
-}
-class AllForOne extends FailOverScheme {
- override def transform = akka.kernel.AllForOne
-}
-class OneForOne extends FailOverScheme {
- override def transform = akka.kernel.OneForOne
-}
+object Configuration {
+ /**
+ * Create a configuration object from a config file of the given path
+ * and filename. The filename must be relative to the path. The path is
+ * used to resolve filenames given in "include" lines.
+ */
+ def fromFile(path: String, filename: String): Configuration = {
+ val config = new Configuration
+ config.loadFile(path, filename)
+ config
+ }
-abstract class Server extends Configuration
-//class kernelConfig(@BeanProperty val restartStrategy: RestartStrategy, @BeanProperty val servers: JList[Server]) extends Server {
-// def transform = akka.kernel.kernelConfig(restartStrategy.transform, servers.toArray.toList.asInstanceOf[List[Server]].map(_.transform))
-//}
-class Component(@BeanProperty val intf: Class[_],
- @BeanProperty val target: Class[_],
- @BeanProperty val lifeCycle: LifeCycle,
- @BeanProperty val timeout: Int) extends Server {
- def newWorker(proxy: TypedActorProxy) = akka.kernel.Supervise(proxy.server, lifeCycle.transform)
+ /**
+ * Create a Configuration object from a config file of the given filename.
+ * The base folder will be extracted from the filename and used as a base
+ * path for resolving filenames given in "include" lines.
+ */
+ def fromFile(filename: String): Configuration = {
+ val n = filename.lastIndexOf('/')
+ if (n < 0) {
+ fromFile(new File(".").getCanonicalPath, filename)
+ } else {
+ fromFile(filename.substring(0, n), filename.substring(n + 1))
+ }
+ }
+
+ /**
+ * Create a Configuration object from the given named resource inside this jar
+ * file, using the system class loader. "include" lines will also operate
+ * on resource paths.
+ */
+ def fromResource(name: String): Configuration = {
+ fromResource(name, ClassLoader.getSystemClassLoader)
+ }
+
+ /**
+ * Create a Configuration object from the given named resource inside this jar
+ * file, using a specific class loader. "include" lines will also operate
+ * on resource paths.
+ */
+ def fromResource(name: String, classLoader: ClassLoader): Configuration = {
+ val config = new Configuration
+ config.importer = new ResourceImporter(classLoader)
+ config.loadFile(name)
+ config
+ }
+
+ /**
+ * Create a Configuration object from a map of String keys and String values.
+ */
+ def fromMap(m: Map[String, String]) = {
+ val config = new Configuration
+ for ((k, v) <- m.elements) {
+ config(k) = v
+ }
+ config
+ }
+
+ /**
+ * Create a Configuration object from a string containing a config file's contents.
+ */
+ def fromString(data: String): Configuration = {
+ val config = new Configuration
+ config.load(data)
+ config
+ }
}
-*/
diff --git a/akka-actor/src/main/scala/akka/config/ConfigurationString.scala b/akka-actor/src/main/scala/akka/config/ConfigurationString.scala
new file mode 100644
index 0000000000..da117801ca
--- /dev/null
+++ b/akka-actor/src/main/scala/akka/config/ConfigurationString.scala
@@ -0,0 +1,117 @@
+/**
+ * Copyright (C) 2009-2011 Scalable Solutions AB
+ *
+ * Based on Configgy by Robey Pointer.
+ * Copyright 2009 Robey Pointer
+ * http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+package akka.config
+
+import scala.util.matching.Regex
+
+
+final class ConfigurationString(wrapped: String) {
+ /**
+ * For every section of a string that matches a regular expression, call
+ * a function to determine a replacement (as in python's
+ * `re.sub`). The function will be passed the Matcher object
+ * corresponding to the substring that matches the pattern, and that
+ * substring will be replaced by the function's result.
+ *
+ * For example, this call:
+ *
+ * "ohio".regexSub("""h.""".r) { m => "n" }
+ *
+ * will return the string `"ono"`.
+ *
+ * The matches are found using `Matcher.find()` and so
+ * will obey all the normal java rules (the matches will not overlap,
+ * etc).
+ *
+ * @param re the regex pattern to replace
+ * @param replace a function that takes Regex.MatchData objects and
+ * returns a string to substitute
+ * @return the resulting string with replacements made
+ */
+ def regexSub(re: Regex)(replace: (Regex.MatchData => String)): String = {
+ var offset = 0
+ var out = new StringBuilder
+
+ for (m <- re.findAllIn(wrapped).matchData) {
+ if (m.start > offset) {
+ out.append(wrapped.substring(offset, m.start))
+ }
+
+ out.append(replace(m))
+ offset = m.end
+ }
+
+ if (offset < wrapped.length) {
+ out.append(wrapped.substring(offset))
+ }
+ out.toString
+ }
+
+ private val QUOTE_RE = "[\u0000-\u001f\u007f-\uffff\\\\\"]".r
+
+ /**
+ * Quote a string so that unprintable chars (in ASCII) are represented by
+ * C-style backslash expressions. For example, a raw linefeed will be
+ * translated into "\n". Control codes (anything below 0x20)
+ * and unprintables (anything above 0x7E) are turned into either
+ * "\xHH" or "\\uHHHH" expressions, depending on
+ * their range. Embedded backslashes and double-quotes are also quoted.
+ *
+ * @return a quoted string, suitable for ASCII display
+ */
+ def quoteC(): String = {
+ regexSub(QUOTE_RE) { m =>
+ m.matched.charAt(0) match {
+ case '\r' => "\\r"
+ case '\n' => "\\n"
+ case '\t' => "\\t"
+ case '"' => "\\\""
+ case '\\' => "\\\\"
+ case c =>
+ if (c <= 255) {
+ "\\x%02x".format(c.asInstanceOf[Int])
+ } else {
+ "\\u%04x" format c.asInstanceOf[Int]
+ }
+ }
+ }
+ }
+
+ // we intentionally don't unquote "\$" here, so it can be used to escape interpolation later.
+ private val UNQUOTE_RE = """\\(u[\dA-Fa-f]{4}|x[\dA-Fa-f]{2}|[/rnt\"\\])""".r
+
+ /**
+ * Unquote an ASCII string that has been quoted in a style like
+ * {@link #quoteC} and convert it into a standard unicode string.
+ * "\\uHHHH" and "\xHH" expressions are unpacked
+ * into unicode characters, as well as "\r", "\n",
+ * "\t", "\\", and '\"'.
+ *
+ * @return an unquoted unicode string
+ */
+ def unquoteC() = {
+ regexSub(UNQUOTE_RE) { m =>
+ val ch = m.group(1).charAt(0) match {
+ // holy crap! this is terrible:
+ case 'u' => Character.valueOf(Integer.valueOf(m.group(1).substring(1), 16).asInstanceOf[Int].toChar)
+ case 'x' => Character.valueOf(Integer.valueOf(m.group(1).substring(1), 16).asInstanceOf[Int].toChar)
+ case 'r' => '\r'
+ case 'n' => '\n'
+ case 't' => '\t'
+ case x => x
+ }
+ ch.toString
+ }
+ }
+}
+
+object string {
+ implicit def stringToConfigurationString(s: String): ConfigurationString = new ConfigurationString(s)
+}
+
diff --git a/akka-actor/src/main/scala/akka/config/Configure.scala b/akka-actor/src/main/scala/akka/config/Configure.scala
new file mode 100644
index 0000000000..90c4680950
--- /dev/null
+++ b/akka-actor/src/main/scala/akka/config/Configure.scala
@@ -0,0 +1,41 @@
+/**
+ * Copyright (C) 2009-2011 Scalable Solutions AB
+ *
+ * Based on Configgy by Robey Pointer.
+ * Copyright 2009 Robey Pointer
+ * http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+package akka.config
+
+import java.io.File
+
+object Configure {
+ private var _config: Configuration = null
+
+ def config = _config
+
+ def config_=(c: Configuration) {
+ _config = c
+ }
+
+ def configure(path: String, filename: String): Unit = {
+ config = Configuration.fromFile(path, filename)
+ }
+
+ def configure(filename: String): Unit = {
+ config = Configuration.fromFile(filename)
+ }
+
+ def configureFromResource(name: String) = {
+ config = Configuration.fromResource(name)
+ }
+
+ def configureFromResource(name: String, classLoader: ClassLoader) = {
+ config = Configuration.fromResource(name, classLoader)
+ }
+
+ def configureFromString(data: String) = {
+ config = Configuration.fromString(data)
+ }
+}
diff --git a/akka-actor/src/main/scala/akka/config/EnvironmentAttributes.scala b/akka-actor/src/main/scala/akka/config/EnvironmentAttributes.scala
new file mode 100644
index 0000000000..ee1d417224
--- /dev/null
+++ b/akka-actor/src/main/scala/akka/config/EnvironmentAttributes.scala
@@ -0,0 +1,83 @@
+/**
+ * Copyright (C) 2009-2011 Scalable Solutions AB
+ *
+ * Based on Configgy by Robey Pointer.
+ * Copyright 2009 Robey Pointer
+ * http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+package akka.config
+
+import java.net.InetAddress
+import scala.collection.{immutable, mutable}
+import scala.collection.JavaConversions
+
+/**
+ * A ConfigMap that wraps the system environment. This is used as a
+ * fallback when looking up "$(...)" substitutions in config files.
+ */
+private[config] object EnvironmentAttributes extends ConfigMap {
+
+ private val env = immutable.Map.empty[String, String] ++ (JavaConversions.asMap(System.getenv()).elements)
+
+ // deal with java.util.Properties extending
+ // java.util.Hashtable[Object, Object] and not
+ // java.util.Hashtable[String, String]
+ private def getSystemProperties(): mutable.HashMap[String,String] = {
+ val map = new mutable.HashMap[String, String]
+ for (entry <- JavaConversions.asMap(System.getProperties()).elements) {
+ entry match {
+ case (k: String, v: String) => map.put(k, v)
+ case _ =>
+ }
+ }
+ map
+ }
+
+ def getName() = ""
+
+ def getString(key: String): Option[String] = {
+ getSystemProperties().get(key).orElse(env.get(key))
+ }
+
+ def getConfigMap(key: String): Option[ConfigMap] = None
+ def configMap(key: String): ConfigMap = error("not implemented")
+
+ def getList(key: String): Seq[String] = getString(key) match {
+ case None => Array[String]()
+ case Some(x) => Array[String](x)
+ }
+
+ def setString(key: String, value: String): Unit = error("read-only attributes")
+ def setList(key: String, value: Seq[String]): Unit = error("read-only attributes")
+ def setConfigMap(key: String, value: ConfigMap): Unit = error("read-only attributes")
+
+ def contains(key: String): Boolean = {
+ env.contains(key) || getSystemProperties().contains(key)
+ }
+
+ def remove(key: String): Boolean = error("read-only attributes")
+ def keys: Iterator[String] = (getSystemProperties().keySet ++ env.keySet).elements
+ def asMap(): Map[String, String] = error("not implemented")
+ def toConfigString = error("not implemented")
+ def copy(): ConfigMap = this
+ def copyInto[T <: ConfigMap](m: T) = m
+ def inheritFrom: Option[ConfigMap] = None
+ def inheritFrom_=(config: Option[ConfigMap]) = error("not implemented")
+
+
+ try {
+ val addr = InetAddress.getLocalHost
+ val ip = addr.getHostAddress
+ val dns = addr.getHostName
+
+ if (ip ne null) {
+ env("HOSTIP") = ip
+ }
+ if (dns ne null) {
+ env("HOSTNAME") = dns
+ }
+ } catch {
+ case _ => // pass
+ }
+}
diff --git a/akka-actor/src/main/scala/akka/config/Importer.scala b/akka-actor/src/main/scala/akka/config/Importer.scala
new file mode 100644
index 0000000000..c1204032ef
--- /dev/null
+++ b/akka-actor/src/main/scala/akka/config/Importer.scala
@@ -0,0 +1,109 @@
+/**
+ * Copyright (C) 2009-2011 Scalable Solutions AB
+ *
+ * Based on Configgy by Robey Pointer.
+ * Copyright 2009 Robey Pointer
+ * http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+package akka.config
+
+import java.io.{BufferedReader, File, FileInputStream, InputStream, InputStreamReader}
+
+
+/**
+ * An interface for finding config files and reading them into strings for
+ * parsing. This is used to handle `include` directives in config files.
+ */
+trait Importer {
+ /**
+ * Imports a requested file and returns the string contents of that file,
+ * if the file exists, and empty string if it does not exist and `required`
+ * is false.
+ *
+ * If the file couldn't be imported, throws a `ParseException`.
+ */
+ @throws(classOf[ParseException])
+ def importFile(filename: String, required: Boolean): String
+
+ /**
+ * Imports a requested file and returns the string contents of that file.
+ * If the file couldn't be imported, throws a `ParseException`.
+ */
+ @throws(classOf[ParseException])
+ def importFile(filename: String): String = importFile(filename, true)
+
+ private val BUFFER_SIZE = 8192
+
+ /**
+ * Exhaustively reads an InputStream and converts it into a String (using
+ * UTF-8 encoding). This is meant as a helper function for custom Importer
+ * classes.
+ *
+ * No exceptions are caught!
+ */
+ protected def streamToString(in: InputStream): String = {
+ val reader = new BufferedReader(new InputStreamReader(in, "UTF-8"))
+ val buffer = new Array[Char](BUFFER_SIZE)
+ val out = new StringBuilder
+ var n = 0
+ while (n >= 0) {
+ n = reader.read(buffer, 0, buffer.length)
+ if (n >= 0) {
+ out.append(buffer, 0, n)
+ }
+ }
+ try {
+ in.close()
+ } catch {
+ case _ =>
+ }
+ out.toString
+ }
+}
+
+
+/**
+ * An Importer that looks for imported config files in the filesystem.
+ * This is the default importer.
+ */
+class FilesystemImporter(val baseFolder: String) extends Importer {
+ def importFile(filename: String, required: Boolean): String = {
+ var f = new File(filename)
+ if (! f.isAbsolute) {
+ f = new File(baseFolder, filename)
+ }
+ if (!required && !f.exists) {
+ ""
+ } else {
+ try {
+ streamToString(new FileInputStream(f))
+ } catch {
+ case x => throw new ParseException(x.toString)
+ }
+ }
+ }
+}
+
+
+/**
+ * An Importer that looks for imported config files in the java resources
+ * of the system class loader (usually the jar used to launch this app).
+ */
+class ResourceImporter(classLoader: ClassLoader) extends Importer {
+ def importFile(filename: String, required: Boolean): String = {
+ try {
+ val stream = classLoader.getResourceAsStream(filename)
+ if (stream eq null) {
+ if (required) {
+ throw new ParseException("Can't find resource: " + filename)
+ }
+ ""
+ } else {
+ streamToString(stream)
+ }
+ } catch {
+ case x => throw new ParseException(x.toString)
+ }
+ }
+}
diff --git a/akka-actor/src/main/scala/akka/dispatch/Dispatchers.scala b/akka-actor/src/main/scala/akka/dispatch/Dispatchers.scala
index 357b5e9e80..616384ae43 100644
--- a/akka-actor/src/main/scala/akka/dispatch/Dispatchers.scala
+++ b/akka-actor/src/main/scala/akka/dispatch/Dispatchers.scala
@@ -9,7 +9,7 @@ import akka.actor.newUuid
import akka.config.Config._
import akka.util.{Duration}
-import net.lag.configgy.ConfigMap
+import akka.config.ConfigMap
import java.util.concurrent.ThreadPoolExecutor.{AbortPolicy, CallerRunsPolicy, DiscardOldestPolicy, DiscardPolicy}
import java.util.concurrent.TimeUnit
diff --git a/akka-actor/src/test/scala/akka/dispatch/DispatchersSpec.scala b/akka-actor/src/test/scala/akka/dispatch/DispatchersSpec.scala
index d09c088c99..984a5e91f6 100644
--- a/akka-actor/src/test/scala/akka/dispatch/DispatchersSpec.scala
+++ b/akka-actor/src/test/scala/akka/dispatch/DispatchersSpec.scala
@@ -7,7 +7,7 @@ import java.util.concurrent.{CountDownLatch, TimeUnit}
import org.scalatest.junit.JUnitSuite
import org.junit.Test
-import net.lag.configgy.Config
+import akka.config.Configuration
import scala.reflect.{Manifest}
import akka.dispatch._
@@ -35,7 +35,7 @@ object DispatchersSpec {
def validTypes = typesAndValidators.keys.toList
lazy val allDispatchers: Map[String,Option[MessageDispatcher]] = {
- validTypes.map(t => (t,from(Config.fromMap(Map(tipe -> t))))).toMap
+ validTypes.map(t => (t,from(Configuration.fromMap(Map(tipe -> t))))).toMap
}
}
@@ -45,12 +45,12 @@ class DispatchersSpec extends JUnitSuite {
import DispatchersSpec._
@Test def shouldYieldNoneIfTypeIsMissing {
- assert(from(Config.fromMap(Map())) === None)
+ assert(from(Configuration.fromMap(Map())) === None)
}
@Test(expected = classOf[IllegalArgumentException])
def shouldThrowIllegalArgumentExceptionIfTypeDoesntExist {
- from(Config.fromMap(Map(tipe -> "typedoesntexist")))
+ from(Configuration.fromMap(Map(tipe -> "typedoesntexist")))
}
@Test def shouldGetTheCorrectTypesOfDispatchers {
@@ -61,7 +61,7 @@ class DispatchersSpec extends JUnitSuite {
}
@Test def defaultingToDefaultWhileLoadingTheDefaultShouldWork {
- assert(from(Config.fromMap(Map())).getOrElse(defaultGlobalDispatcher) == defaultGlobalDispatcher)
+ assert(from(Configuration.fromMap(Map())).getOrElse(defaultGlobalDispatcher) == defaultGlobalDispatcher)
}
}
diff --git a/config/akka-reference.conf b/config/akka-reference.conf
index 6aa1432c8a..6a5c5559e0 100644
--- a/config/akka-reference.conf
+++ b/config/akka-reference.conf
@@ -11,8 +11,6 @@ akka {
enabled-modules = [] # Comma separated list of the enabled modules. Options: ["remote", "camel", "http"]
time-unit = "seconds" # Time unit for all timeout properties throughout the config
-
- enable-jmx = on # expose the configuration through JMX
default-error-handler = on # register the default error handler listener which logs errors to STDOUT
diff --git a/embedded-repo/com/facebook/thrift/1.0/thrift-1.0.jar b/embedded-repo/com/facebook/thrift/1.0/thrift-1.0.jar
deleted file mode 100644
index 726e66523d..0000000000
Binary files a/embedded-repo/com/facebook/thrift/1.0/thrift-1.0.jar and /dev/null differ
diff --git a/embedded-repo/com/facebook/thrift/1.0/thrift-1.0.pom b/embedded-repo/com/facebook/thrift/1.0/thrift-1.0.pom
deleted file mode 100644
index cef1963e19..0000000000
--- a/embedded-repo/com/facebook/thrift/1.0/thrift-1.0.pom
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
- 4.0.0
- com.facebook
- thrift
- 1.0
- jar
-
\ No newline at end of file
diff --git a/embedded-repo/com/facebook/thrift/r917130/thrift-r917130.jar b/embedded-repo/com/facebook/thrift/r917130/thrift-r917130.jar
deleted file mode 100644
index 896cdb2af8..0000000000
Binary files a/embedded-repo/com/facebook/thrift/r917130/thrift-r917130.jar and /dev/null differ
diff --git a/embedded-repo/com/facebook/thrift/r917130/thrift-r917130.pom b/embedded-repo/com/facebook/thrift/r917130/thrift-r917130.pom
deleted file mode 100644
index 23b4177b38..0000000000
--- a/embedded-repo/com/facebook/thrift/r917130/thrift-r917130.pom
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
- 4.0.0
- com.facebook
- thrift
- r917130
- jar
-
\ No newline at end of file
diff --git a/embedded-repo/net/lag/configgy/2.0.2-nologgy/configgy-2.0.2-nologgy.jar b/embedded-repo/net/lag/configgy/2.0.2-nologgy/configgy-2.0.2-nologgy.jar
deleted file mode 100644
index 8a6a0a0e5b..0000000000
Binary files a/embedded-repo/net/lag/configgy/2.0.2-nologgy/configgy-2.0.2-nologgy.jar and /dev/null differ
diff --git a/embedded-repo/net/lag/configgy/2.0.2-nologgy/configgy-2.0.2-nologgy.pom b/embedded-repo/net/lag/configgy/2.0.2-nologgy/configgy-2.0.2-nologgy.pom
deleted file mode 100644
index 9238472de5..0000000000
--- a/embedded-repo/net/lag/configgy/2.0.2-nologgy/configgy-2.0.2-nologgy.pom
+++ /dev/null
@@ -1,83 +0,0 @@
-
-
- 4.0.0
- net.lag
- configgy
- jar
- 2.0.2-nologgy
- Configgy
- Configgy logging removed
- http://github.com/derekjw/configgy
-
-
- Apache 2
- http://www.apache.org/licenses/LICENSE-2.0.txt
- repo
-
-
-
-
- org.scala-lang
- scala-library
- 2.8.1
- compile
-
-
-
-
- scalatoolsorg
- scala-tools.org
- http://scala-tools.org/repo-releases/
-
-
- atlassian
- atlassian
- https://m2proxy.atlassian.com/repository/public/
-
-
- lagnet
- lag.net
- http://www.lag.net/repo/
-
-
- testingscalatoolsorg
- testing.scala-tools.org
- http://scala-tools.org/repo-releases/testing/
-
-
- oauthnet
- oauth.net
- http://oauth.googlecode.com/svn/code/maven/
-
-
- downloadjavanet
- download.java.net
- http://download.java.net/maven/2/
-
-
- oldtwittercom
- old.twitter.com
- http://www.lag.net/nest/
-
-
- twittercom
- twitter.com
- http://maven.twttr.com/
-
-
- powermockapi
- powermock-api
- http://powermock.googlecode.com/svn/repo/
-
-
- ibiblio
- ibiblio
- http://mirrors.ibiblio.org/pub/mirrors/maven2/
-
-
- ScalaToolsMaven2Repository
- Scala-Tools Maven2 Repository
- http://scala-tools.org/repo-releases/
-
-
-
\ No newline at end of file
diff --git a/embedded-repo/net/lag/configgy/maven-metadata-local.xml b/embedded-repo/net/lag/configgy/maven-metadata-local.xml
deleted file mode 100644
index 8992b6209e..0000000000
--- a/embedded-repo/net/lag/configgy/maven-metadata-local.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
- net.lag
- configgy
- 2.8.0.RC2-1.5.2-SNAPSHOT
-
-
- 2.8.0.RC2-1.5.2-SNAPSHOT
-
- 20100519155407
-
-
diff --git a/project/build/AkkaProject.scala b/project/build/AkkaProject.scala
index f15462e694..444ca67785 100644
--- a/project/build/AkkaProject.scala
+++ b/project/build/AkkaProject.scala
@@ -19,7 +19,7 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) {
val scalaCompileSettings =
Seq("-deprecation",
"-Xmigration",
- "-Xcheckinit",
+ //"-Xcheckinit", //Never use this for anything but debugging
"-optimise",
"-Xwarninit",
"-encoding", "utf8")
@@ -133,8 +133,6 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) {
lazy val commons_io = "commons-io" % "commons-io" % "2.0.1" % "compile" //ApacheV2
- lazy val configgy = "net.lag" % "configgy" % "2.0.2-nologgy" % "compile" //ApacheV2
-
lazy val javax_servlet_30 = "org.glassfish" % "javax.servlet" % JAVAX_SERVLET_VERSION % "provided" //CDDL v1
lazy val jetty = "org.eclipse.jetty" % "jetty-server" % JETTY_VERSION % "compile" //Eclipse license
@@ -297,8 +295,6 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) {
// -------------------------------------------------------------------------------------------------------------------
class AkkaActorProject(info: ProjectInfo) extends AkkaDefaultProject(info, distPath) {
- val configgy = Dependencies.configgy
-
// testing
val junit = Dependencies.junit
val scalatest = Dependencies.scalatest