Merge branch 'master' into wip-1378-fixme-patriknw

Conflicts:
	akka-actor/src/main/scala/akka/actor/ActorRef.scala
	akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala
	akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala
This commit is contained in:
Patrik Nordwall 2011-12-02 09:03:03 +01:00
commit 82bbca43ab
112 changed files with 3126 additions and 1458 deletions

View file

@ -7,7 +7,6 @@ import org.junit.Test;
import com.typesafe.config.ConfigFactory; import com.typesafe.config.ConfigFactory;
import com.typesafe.config.Config; import com.typesafe.config.Config;
import com.typesafe.config.ConfigParseOptions;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@ -26,14 +25,14 @@ public class JavaExtension {
} }
static class TestExtension implements Extension { static class TestExtension implements Extension {
public final ActorSystemImpl system; public final ActorSystemImpl system;
public TestExtension(ActorSystemImpl i) {
system = i; public TestExtension(ActorSystemImpl i) {
} system = i;
}
} }
private Config c = ConfigFactory.parseString("akka.extensions = [ \"akka.actor.JavaExtension$Provider\" ]", private Config c = ConfigFactory.parseString("akka.extensions = [ \"akka.actor.JavaExtension$Provider\" ]");
ConfigParseOptions.defaults());
private ActorSystem system = ActorSystem.create("JavaExtension", c); private ActorSystem system = ActorSystem.create("JavaExtension", c);

View file

@ -0,0 +1,61 @@
package akka.actor
import akka.testkit.AkkaSpec
import akka.dispatch.UnboundedMailbox
import akka.util.duration._
object ConsistencySpec {
class CacheMisaligned(var value: Long, var padding1: Long, var padding2: Long, var padding3: Int) //Vars, no final fences
class ConsistencyCheckingActor extends Actor {
var left = new CacheMisaligned(42, 0, 0, 0) //var
var right = new CacheMisaligned(0, 0, 0, 0) //var
var lastStep = -1L
def receive = {
case step: Long
if (lastStep != (step - 1))
sender.tell("Test failed: Last step %s, this step %s".format(lastStep, step))
var shouldBeFortyTwo = left.value + right.value
if (shouldBeFortyTwo != 42)
sender ! "Test failed: 42 failed"
else {
left.value += 1
right.value -= 1
}
lastStep = step
case "done" sender ! "done"; self.stop()
}
}
}
class ConsistencySpec extends AkkaSpec {
import ConsistencySpec._
"The Akka actor model implementation" must {
"provide memory consistency" in {
val noOfActors = 7
val dispatcher = system
.dispatcherFactory
.newDispatcher("consistency-dispatcher", 1, UnboundedMailbox())
.withNewThreadPoolWithArrayBlockingQueueWithCapacityAndFairness(noOfActors, true)
.setCorePoolSize(10)
.setMaxPoolSize(10)
.setKeepAliveTimeInMillis(1)
.setAllowCoreThreadTimeout(true)
.build
val props = Props[ConsistencyCheckingActor].withDispatcher(dispatcher)
val actors = Vector.fill(noOfActors)(system.actorOf(props))
for (i 0L until 600000L) {
actors.foreach(_.tell(i, testActor))
}
for (a actors) { a.tell("done", testActor) }
for (a actors) expectMsg(5 minutes, "done")
}
}
}

View file

@ -11,6 +11,10 @@ import java.util.concurrent.atomic._
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) @org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
class DeathWatchSpec extends AkkaSpec with BeforeAndAfterEach with ImplicitSender { class DeathWatchSpec extends AkkaSpec with BeforeAndAfterEach with ImplicitSender {
def startWatching(target: ActorRef) = actorOf(Props(new Actor {
watch(target)
def receive = { case x testActor forward x }
}))
"The Death Watch" must { "The Death Watch" must {
def expectTerminationOf(actorRef: ActorRef) = expectMsgPF(5 seconds, actorRef + ": Stopped or Already terminated when linking") { def expectTerminationOf(actorRef: ActorRef) = expectMsgPF(5 seconds, actorRef + ": Stopped or Already terminated when linking") {
@ -19,8 +23,7 @@ class DeathWatchSpec extends AkkaSpec with BeforeAndAfterEach with ImplicitSende
"notify with one Terminated message when an Actor is stopped" in { "notify with one Terminated message when an Actor is stopped" in {
val terminal = actorOf(Props(context { case _ })) val terminal = actorOf(Props(context { case _ }))
startWatching(terminal)
testActor startsWatching terminal
testActor ! "ping" testActor ! "ping"
expectMsg("ping") expectMsg("ping")
@ -32,11 +35,7 @@ class DeathWatchSpec extends AkkaSpec with BeforeAndAfterEach with ImplicitSende
"notify with all monitors with one Terminated message when an Actor is stopped" in { "notify with all monitors with one Terminated message when an Actor is stopped" in {
val terminal = actorOf(Props(context { case _ })) val terminal = actorOf(Props(context { case _ }))
val monitor1, monitor2, monitor3 = val monitor1, monitor2, monitor3 = startWatching(terminal)
actorOf(Props(new Actor {
watch(terminal)
def receive = { case t: Terminated testActor ! t }
}))
terminal ! PoisonPill terminal ! PoisonPill
@ -51,11 +50,7 @@ class DeathWatchSpec extends AkkaSpec with BeforeAndAfterEach with ImplicitSende
"notify with _current_ monitors with one Terminated message when an Actor is stopped" in { "notify with _current_ monitors with one Terminated message when an Actor is stopped" in {
val terminal = actorOf(Props(context { case _ })) val terminal = actorOf(Props(context { case _ }))
val monitor1, monitor3 = val monitor1, monitor3 = startWatching(terminal)
actorOf(Props(new Actor {
watch(terminal)
def receive = { case t: Terminated testActor ! t }
}))
val monitor2 = actorOf(Props(new Actor { val monitor2 = actorOf(Props(new Actor {
watch(terminal) watch(terminal)
unwatch(terminal) unwatch(terminal)
@ -85,10 +80,7 @@ class DeathWatchSpec extends AkkaSpec with BeforeAndAfterEach with ImplicitSende
val terminalProps = Props(context { case x context.sender ! x }) val terminalProps = Props(context { case x context.sender ! x })
val terminal = (supervisor ? terminalProps).as[ActorRef].get val terminal = (supervisor ? terminalProps).as[ActorRef].get
val monitor = actorOf(Props(new Actor { val monitor = startWatching(terminal)
watch(terminal)
def receive = { case t: Terminated testActor ! t }
}))
terminal ! Kill terminal ! Kill
terminal ! Kill terminal ! Kill
@ -113,9 +105,13 @@ class DeathWatchSpec extends AkkaSpec with BeforeAndAfterEach with ImplicitSende
} }
})) }))
val failed, brother = (supervisor ? Props.empty).as[ActorRef].get val failed = (supervisor ? Props.empty).as[ActorRef].get
brother startsWatching failed val brother = (supervisor ? Props(new Actor {
testActor startsWatching brother watch(failed)
def receive = Actor.emptyBehavior
})).as[ActorRef].get
startWatching(brother)
failed ! Kill failed ! Kill
val result = receiveWhile(3 seconds, messages = 3) { val result = receiveWhile(3 seconds, messages = 3) {

View file

@ -58,7 +58,7 @@ class FSMTransitionSpec extends AkkaSpec with ImplicitSender {
val forward = actorOf(new Forwarder(testActor)) val forward = actorOf(new Forwarder(testActor))
val fsm = actorOf(new MyFSM(testActor)) val fsm = actorOf(new MyFSM(testActor))
val sup = actorOf(Props(new Actor { val sup = actorOf(Props(new Actor {
self startsWatching fsm watch(fsm)
def receive = { case _ } def receive = { case _ }
}).withFaultHandler(OneForOneStrategy(List(classOf[Throwable]), None, None))) }).withFaultHandler(OneForOneStrategy(List(classOf[Throwable]), None, None)))

View file

@ -206,7 +206,7 @@ class RestartStrategySpec extends AkkaSpec {
val boss = actorOf(Props(new Actor { val boss = actorOf(Props(new Actor {
def receive = { def receive = {
case p: Props sender ! context.actorOf(p) case p: Props sender ! watch(context.actorOf(p))
case t: Terminated maxNoOfRestartsLatch.open case t: Terminated maxNoOfRestartsLatch.open
} }
}).withFaultHandler(OneForOneStrategy(List(classOf[Throwable]), None, Some(1000)))) }).withFaultHandler(OneForOneStrategy(List(classOf[Throwable]), None, Some(1000))))
@ -228,8 +228,6 @@ class RestartStrategySpec extends AkkaSpec {
}) })
val slave = (boss ? slaveProps).as[ActorRef].get val slave = (boss ? slaveProps).as[ActorRef].get
boss startsWatching slave
slave ! Ping slave ! Ping
slave ! Crash slave ! Crash
slave ! Ping slave ! Ping

View file

@ -52,8 +52,7 @@ class SupervisorHierarchySpec extends AkkaSpec {
val countDownMessages = new CountDownLatch(1) val countDownMessages = new CountDownLatch(1)
val countDownMax = new CountDownLatch(1) val countDownMax = new CountDownLatch(1)
val boss = actorOf(Props(new Actor { val boss = actorOf(Props(new Actor {
val crasher = context.actorOf(Props(new CountDownActor(countDownMessages))) val crasher = watch(context.actorOf(Props(new CountDownActor(countDownMessages))))
self startsWatching crasher
protected def receive = { protected def receive = {
case "killCrasher" crasher ! Kill case "killCrasher" crasher ! Kill

View file

@ -341,9 +341,11 @@ abstract class ActorModelSpec extends AkkaSpec {
val cachedMessage = CountDownNStop(new CountDownLatch(num)) val cachedMessage = CountDownNStop(new CountDownLatch(num))
val stopLatch = new CountDownLatch(num) val stopLatch = new CountDownLatch(num)
val waitTime = (30 seconds).dilated.toMillis val waitTime = (30 seconds).dilated.toMillis
val boss = actorOf(Props(context { val boss = actorOf(Props(new Actor {
case "run" for (_ 1 to num) (context.self startsWatching context.actorOf(props)) ! cachedMessage def receive = {
case Terminated(child) stopLatch.countDown() case "run" for (_ 1 to num) (watch(context.actorOf(props))) ! cachedMessage
case Terminated(child) stopLatch.countDown()
}
}).withDispatcher(system.dispatcherFactory.newPinnedDispatcher("boss"))) }).withDispatcher(system.dispatcherFactory.newPinnedDispatcher("boss")))
boss ! "run" boss ! "run"
try { try {
@ -492,3 +494,39 @@ class BalancingDispatcherModelSpec extends ActorModelSpec {
} }
} }
} }
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
class FJDispatcherModelSpec extends ActorModelSpec {
import ActorModelSpec._
def newInterceptedDispatcher =
(new Dispatcher(system.dispatcherFactory.prerequisites, "foo", system.settings.DispatcherThroughput,
system.settings.DispatcherThroughputDeadlineTime, system.dispatcherFactory.MailboxType,
new ForkJoinPoolConfig(), system.settings.DispatcherDefaultShutdown) with MessageDispatcherInterceptor).asInstanceOf[MessageDispatcherInterceptor]
def dispatcherType = "FJDispatcher"
"A " + dispatcherType must {
"process messages in parallel" in {
implicit val dispatcher = newInterceptedDispatcher
val aStart, aStop, bParallel = new CountDownLatch(1)
val a, b = newTestActor(dispatcher)
a ! Meet(aStart, aStop)
assertCountDown(aStart, 3.seconds.dilated.toMillis, "Should process first message within 3 seconds")
b ! CountDown(bParallel)
assertCountDown(bParallel, 3.seconds.dilated.toMillis, "Should process other actors in parallel")
aStop.countDown()
a.stop
b.stop
while (!a.isTerminated && !b.isTerminated) {} //Busy wait for termination
assertRefDefaultZero(a)(registers = 1, unregisters = 1, msgsReceived = 1, msgsProcessed = 1)
assertRefDefaultZero(b)(registers = 1, unregisters = 1, msgsReceived = 1, msgsProcessed = 1)
}
}
}

View file

@ -9,7 +9,6 @@ import akka.dispatch._
import akka.testkit.AkkaSpec import akka.testkit.AkkaSpec
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) @org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
class DispatchersSpec extends AkkaSpec { class DispatchersSpec extends AkkaSpec {
@ -41,7 +40,7 @@ class DispatchersSpec extends AkkaSpec {
throughput = 17 throughput = 17
} }
} }
""", ConfigParseOptions.defaults) """)
lazy val allDispatchers: Map[String, Option[MessageDispatcher]] = { lazy val allDispatchers: Map[String, Option[MessageDispatcher]] = {
validTypes.map(t (t, from(ConfigFactory.parseMap(Map(tipe -> t).asJava).withFallback(defaultDispatcherConfig)))).toMap validTypes.map(t (t, from(ConfigFactory.parseMap(Map(tipe -> t).asJava).withFallback(defaultDispatcherConfig)))).toMap

View file

@ -6,13 +6,12 @@ package akka.config
import akka.testkit.AkkaSpec import akka.testkit.AkkaSpec
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
import akka.util.duration._ import akka.util.duration._
import akka.util.Duration import akka.util.Duration
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) @org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
class ConfigSpec extends AkkaSpec(ConfigFactory.parseResource(classOf[ConfigSpec], "/akka-actor-reference.conf", ConfigParseOptions.defaults)) { class ConfigSpec extends AkkaSpec(ConfigFactory.defaultReference) {
"The default configuration file (i.e. akka-actor-reference.conf)" must { "The default configuration file (i.e. akka-actor-reference.conf)" must {
"contain all configuration properties for akka-actor that are used in code with their correct defaults" in { "contain all configuration properties for akka-actor that are used in code with their correct defaults" in {

View file

@ -7,7 +7,6 @@ import akka.testkit.AkkaSpec
import akka.util.duration._ import akka.util.duration._
import akka.actor.{ Actor, ActorRef, ActorSystemImpl } import akka.actor.{ Actor, ActorRef, ActorSystemImpl }
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
import akka.actor.ActorSystem import akka.actor.ActorSystem
@ -19,7 +18,7 @@ object EventStreamSpec {
loglevel = INFO loglevel = INFO
event-handlers = ["akka.event.EventStreamSpec$MyLog", "%s"] event-handlers = ["akka.event.EventStreamSpec$MyLog", "%s"]
} }
""".format(Logging.StandardOutLoggerName), ConfigParseOptions.defaults) """.format(Logging.StandardOutLoggerName))
case class M(i: Int) case class M(i: Int)

View file

@ -9,7 +9,6 @@ import akka.testkit._
import org.scalatest.WordSpec import org.scalatest.WordSpec
import akka.util.Duration import akka.util.Duration
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
import java.util.Properties import java.util.Properties
import akka.actor.Actor import akka.actor.Actor

View file

@ -222,7 +222,7 @@ class Report(
sb.append("Akka version: ").append(system.settings.ConfigVersion) sb.append("Akka version: ").append(system.settings.ConfigVersion)
sb.append("\n") sb.append("\n")
sb.append("Akka config:") sb.append("Akka config:")
for ((key, value) system.settings.config.toObject) { for ((key, value) system.settings.config.root) {
sb.append("\n ").append(key).append("=").append(value) sb.append("\n ").append(key).append("=").append(value)
} }

View file

@ -11,7 +11,6 @@ import akka.actor.{ ActorSystem, ActorSystemImpl }
import java.io.{ ObjectInputStream, ByteArrayInputStream, ByteArrayOutputStream, ObjectOutputStream } import java.io.{ ObjectInputStream, ByteArrayInputStream, ByteArrayOutputStream, ObjectOutputStream }
import akka.actor.DeadLetterActorRef import akka.actor.DeadLetterActorRef
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
object SerializeSpec { object SerializeSpec {
@ -32,7 +31,7 @@ object SerializeSpec {
} }
} }
} }
""", ConfigParseOptions.defaults) """)
@BeanInfo @BeanInfo
case class Address(no: String, street: String, city: String, zip: String) { def this() = this("", "", "", "") } case class Address(no: String, street: String, city: String, zip: String) { def this() = this("", "", "", "") }

View file

@ -6,51 +6,82 @@ package com.typesafe.config;
import java.util.List; import java.util.List;
/** /**
* This class represents an immutable map from config paths to config values. It * An immutable map from config paths to config values.
* also contains some static methods for creating configs.
* *
* <p>
* Contrast with {@link ConfigObject} which is a map from config <em>keys</em>,
* rather than paths, to config values. A {@code Config} contains a tree of
* {@code ConfigObject}, and {@link Config#root()} returns the tree's root
* object.
*
* <p>
* Throughout the API, there is a distinction between "keys" and "paths". A key * Throughout the API, there is a distinction between "keys" and "paths". A key
* is a key in a JSON object; it's just a string that's the key in a map. A * is a key in a JSON object; it's just a string that's the key in a map. A
* "path" is a parseable expression with a syntax and it refers to a series of * "path" is a parseable expression with a syntax and it refers to a series of
* keys. Path expressions are described in the spec for "HOCON", which can be * keys. Path expressions are described in the <a
* found at https://github.com/havocp/config/blob/master/HOCON.md; in brief, a * href="https://github.com/havocp/config/blob/master/HOCON.md">spec for
* path is period-separated so "a.b.c" looks for key c in object b in object a * Human-Optimized Config Object Notation</a>. In brief, a path is
* in the root object. Sometimes double quotes are needed around special * period-separated so "a.b.c" looks for key c in object b in object a in the
* characters in path expressions. * root object. Sometimes double quotes are needed around special characters in
* path expressions.
* *
* The API for a Config is in terms of path expressions, while the API for a * <p>
* ConfigObject is in terms of keys. Conceptually, Config is a one-level map * The API for a {@code Config} is in terms of path expressions, while the API
* from paths to values, while a ConfigObject is a tree of maps from keys to * for a {@code ConfigObject} is in terms of keys. Conceptually, {@code Config}
* values. * is a one-level map from <em>paths</em> to values, while a
* {@code ConfigObject} is a tree of nested maps from <em>keys</em> to values.
* *
* Another difference between Config and ConfigObject is that conceptually, * <p>
* ConfigValue with valueType() of ConfigValueType.NULL exist in a ConfigObject, * Another difference between {@code Config} and {@code ConfigObject} is that
* while a Config treats null values as if they were missing. * conceptually, {@code ConfigValue}s with a {@link ConfigValue#valueType()
* valueType()} of {@link ConfigValueType#NULL NULL} exist in a
* {@code ConfigObject}, while a {@code Config} treats null values as if they
* were missing.
* *
* Config is an immutable object and thus safe to use from multiple threads. * <p>
* {@code Config} is an immutable object and thus safe to use from multiple
* threads. There's never a need for "defensive copies."
* *
* The "getters" on a Config all work in the same way. They never return null, * <p>
* nor do they return a ConfigValue with valueType() of ConfigValueType.NULL. * The "getters" on a {@code Config} all work in the same way. They never return
* Instead, they throw ConfigException.Missing if the value is completely absent * null, nor do they return a {@code ConfigValue} with
* or set to null. If the value is set to null, a subtype of * {@link ConfigValue#valueType() valueType()} of {@link ConfigValueType#NULL
* ConfigException.Missing called ConfigException.Null will be thrown. * NULL}. Instead, they throw {@link ConfigException.Missing} if the value is
* ConfigException.WrongType will be thrown anytime you ask for a type and the * completely absent or set to null. If the value is set to null, a subtype of
* value has an incompatible type. Reasonable type conversions are performed for * {@code ConfigException.Missing} called {@link ConfigException.Null} will be
* you though. * thrown. {@link ConfigException.WrongType} will be thrown anytime you ask for
* a type and the value has an incompatible type. Reasonable type conversions
* are performed for you though.
* *
* If you want to iterate over the contents of a Config, you have to get its * <p>
* ConfigObject with toObject, and then iterate over the ConfigObject. * If you want to iterate over the contents of a {@code Config}, you have to get
* its {@code ConfigObject} with {@link #root()}, and then iterate over the
* {@code ConfigObject}.
* *
*
* <p>
* <em>Do not implement {@code Config}</em>; it should only be implemented by
* the config library. Arbitrary implementations will not work because the
* library internals assume a specific concrete implementation. Also, this
* interface is likely to grow new methods over time, so third-party
* implementations will break.
*/ */
public interface Config extends ConfigMergeable { public interface Config extends ConfigMergeable {
/** /**
* Gets the config as a tree of ConfigObject. This is a constant-time * Gets the {@code Config} as a tree of {@link ConfigObject}. This is a
* operation (it is not proportional to the number of values in the Config). * constant-time operation (it is not proportional to the number of values
* in the {@code Config}).
* *
* @return * @return the root object in the configuration
*/ */
ConfigObject toObject(); ConfigObject root();
/**
* Gets the origin of the {@code Config}, which may be a file, or a file
* with a line number, or just a descriptive phrase.
*
* @return the origin of the {@code Config} for use in error messages
*/
ConfigOrigin origin(); ConfigOrigin origin();
@Override @Override
@ -60,15 +91,157 @@ public interface Config extends ConfigMergeable {
ConfigObject toValue(); ConfigObject toValue();
/** /**
* Checks whether a value is present and non-null at the given path. This * Returns a replacement config with all substitutions (the
* differs in two ways from ConfigObject.containsKey(): it looks for a path * <code>${foo.bar}</code> syntax, see <a
* expression, not a key; and it returns false for null values, while * href="https://github.com/havocp/config/blob/master/HOCON.md">the
* containsKey() returns true indicating that the object contains a null * spec</a>) resolved. Substitutions are looked up using this
* value for the key. * <code>Config</code> as the root object, that is, a substitution
* <code>${foo.bar}</code> will be replaced with the result of
* <code>getValue("foo.bar")</code>.
* *
* If a path exists according to hasPath(), then getValue() will never throw * <p>
* an exception. However, the typed getters, such as getInt(), will still * This method uses {@link ConfigResolveOptions#defaults()}, there is
* throw if the value is not convertible to the requested type. * another variant {@link Config#resolve(ConfigResolveOptions)} which lets
* you specify non-default options.
*
* <p>
* A given {@link Config} must be resolved before using it to retrieve
* config values, but ideally should be resolved one time for your entire
* stack of fallbacks (see {@link Config#withFallback}). Otherwise, some
* substitutions that could have resolved with all fallbacks available may
* not resolve, which will be a user-visible oddity.
*
* <p>
* <code>resolve()</code> should be invoked on root config objects, rather
* than on a subtree (a subtree is the result of something like
* <code>config.getConfig("foo")</code>). The problem with
* <code>resolve()</code> on a subtree is that substitutions are relative to
* the root of the config and the subtree will have no way to get values
* from the root. For example, if you did
* <code>config.getConfig("foo").resolve()</code> on the below config file,
* it would not work:
*
* <pre>
* common-value = 10
* foo {
* whatever = ${common-value}
* }
* </pre>
*
* @return an immutable object with substitutions resolved
* @throws ConfigException.UnresolvedSubstitution
* if any substitutions refer to nonexistent paths
* @throws ConfigException
* some other config exception if there are other problems
*/
Config resolve();
/**
* Like {@link Config#resolve()} but allows you to specify non-default
* options.
*
* @param options
* resolve options
* @return the resolved <code>Config</code>
*/
Config resolve(ConfigResolveOptions options);
/**
* Validates this config against a reference config, throwing an exception
* if it is invalid. The purpose of this method is to "fail early" with a
* comprehensive list of problems; in general, anything this method can find
* would be detected later when trying to use the config, but it's often
* more user-friendly to fail right away when loading the config.
*
* <p>
* Using this method is always optional, since you can "fail late" instead.
*
* <p>
* You must restrict validation to paths you "own" (those whose meaning are
* defined by your code module). If you validate globally, you may trigger
* errors about paths that happen to be in the config but have nothing to do
* with your module. It's best to allow the modules owning those paths to
* validate them. Also, if every module validates only its own stuff, there
* isn't as much redundant work being done.
*
* <p>
* If no paths are specified in <code>checkValid()</code>'s parameter list,
* validation is for the entire config.
*
* <p>
* If you specify paths that are not in the reference config, those paths
* are ignored. (There's nothing to validate.)
*
* <p>
* Here's what validation involves:
*
* <ul>
* <li>All paths found in the reference config must be present in this
* config or an exception will be thrown.
* <li>
* Some changes in type from the reference config to this config will cause
* an exception to be thrown. Not all potential type problems are detected,
* in particular it's assumed that strings are compatible with everything
* except objects and lists. This is because string types are often "really"
* some other type (system properties always start out as strings, or a
* string like "5ms" could be used with {@link #getMilliseconds}). Also,
* it's allowed to set any type to null or override null with any type.
* <li>
* Any unresolved substitutions in this config will cause a validation
* failure; both the reference config and this config should be resolved
* before validation. If the reference config is unresolved, it's a bug in
* the caller of this method.
* </ul>
*
* <p>
* If you want to allow a certain setting to have a flexible type (or
* otherwise want validation to be looser for some settings), you could
* either remove the problematic setting from the reference config provided
* to this method, or you could intercept the validation exception and
* screen out certain problems. Of course, this will only work if all other
* callers of this method are careful to restrict validation to their own
* paths, as they should be.
*
* <p>
* If validation fails, the thrown exception contains a list of all problems
* found. See {@link ConfigException.ValidationFailed#problems}. The
* exception's <code>getMessage()</code> will have all the problems
* concatenated into one huge string, as well.
*
* <p>
* Again, <code>checkValid()</code> can't guess every domain-specific way a
* setting can be invalid, so some problems may arise later when attempting
* to use the config. <code>checkValid()</code> is limited to reporting
* generic, but common, problems such as missing settings and blatant type
* incompatibilities.
*
* @param reference
* a reference configuration
* @param restrictToPaths
* only validate values underneath these paths that your code
* module owns and understands
* @throws ConfigException.ValidationFailed
* if there are any validation issues
* @throws ConfigException.NotResolved
* if this config is not resolved
* @throws ConfigException.BugOrBroken
* if the reference config is unresolved or caller otherwise
* misuses the API
*/
void checkValid(Config reference, String... restrictToPaths);
/**
* Checks whether a value is present and non-null at the given path. This
* differs in two ways from {@code Map.containsKey()} as implemented by
* {@link ConfigObject}: it looks for a path expression, not a key; and it
* returns false for null values, while {@code containsKey()} returns true
* indicating that the object contains a null value for the key.
*
* <p>
* If a path exists according to {@link #hasPath(String)}, then
* {@link #getValue(String)} will never throw an exception. However, the
* typed getters, such as {@link #getInt(String)}, will still throw if the
* value is not convertible to the requested type.
* *
* @param path * @param path
* the path expression * the path expression
@ -78,12 +251,19 @@ public interface Config extends ConfigMergeable {
*/ */
boolean hasPath(String path); boolean hasPath(String path);
/**
* Returns true if the {@code Config}'s root object contains no key-value
* pairs.
*
* @return true if the configuration is empty
*/
boolean isEmpty(); boolean isEmpty();
/** /**
* *
* @param path * @param path
* @return * path expression
* @return the boolean value at the requested path
* @throws ConfigException.Missing * @throws ConfigException.Missing
* if value is absent or null * if value is absent or null
* @throws ConfigException.WrongType * @throws ConfigException.WrongType
@ -93,7 +273,8 @@ public interface Config extends ConfigMergeable {
/** /**
* @param path * @param path
* @return * path expression
* @return the numeric value at the requested path
* @throws ConfigException.Missing * @throws ConfigException.Missing
* if value is absent or null * if value is absent or null
* @throws ConfigException.WrongType * @throws ConfigException.WrongType
@ -103,17 +284,20 @@ public interface Config extends ConfigMergeable {
/** /**
* @param path * @param path
* @return * path expression
* @return the 32-bit integer value at the requested path
* @throws ConfigException.Missing * @throws ConfigException.Missing
* if value is absent or null * if value is absent or null
* @throws ConfigException.WrongType * @throws ConfigException.WrongType
* if value is not convertible to an int * if value is not convertible to an int (for example it is out
* of range, or it's a boolean value)
*/ */
int getInt(String path); int getInt(String path);
/** /**
* @param path * @param path
* @return * path expression
* @return the 64-bit long value at the requested path
* @throws ConfigException.Missing * @throws ConfigException.Missing
* if value is absent or null * if value is absent or null
* @throws ConfigException.WrongType * @throws ConfigException.WrongType
@ -123,7 +307,8 @@ public interface Config extends ConfigMergeable {
/** /**
* @param path * @param path
* @return * path expression
* @return the floating-point value at the requested path
* @throws ConfigException.Missing * @throws ConfigException.Missing
* if value is absent or null * if value is absent or null
* @throws ConfigException.WrongType * @throws ConfigException.WrongType
@ -133,7 +318,8 @@ public interface Config extends ConfigMergeable {
/** /**
* @param path * @param path
* @return * path expression
* @return the string value at the requested path
* @throws ConfigException.Missing * @throws ConfigException.Missing
* if value is absent or null * if value is absent or null
* @throws ConfigException.WrongType * @throws ConfigException.WrongType
@ -143,7 +329,8 @@ public interface Config extends ConfigMergeable {
/** /**
* @param path * @param path
* @return * path expression
* @return the {@link ConfigObject} value at the requested path
* @throws ConfigException.Missing * @throws ConfigException.Missing
* if value is absent or null * if value is absent or null
* @throws ConfigException.WrongType * @throws ConfigException.WrongType
@ -153,7 +340,8 @@ public interface Config extends ConfigMergeable {
/** /**
* @param path * @param path
* @return * path expression
* @return the nested {@code Config} value at the requested path
* @throws ConfigException.Missing * @throws ConfigException.Missing
* if value is absent or null * if value is absent or null
* @throws ConfigException.WrongType * @throws ConfigException.WrongType
@ -162,9 +350,13 @@ public interface Config extends ConfigMergeable {
Config getConfig(String path); Config getConfig(String path);
/** /**
* Gets the value at the path as an unwrapped Java boxed value (Boolean, * Gets the value at the path as an unwrapped Java boxed value (
* Integer, Long, etc.) * {@link java.lang.Boolean Boolean}, {@link java.lang.Integer Integer}, and
* so on - see {@link ConfigValue#unwrapped()}).
* *
* @param path
* path expression
* @return the unwrapped value at the requested path
* @throws ConfigException.Missing * @throws ConfigException.Missing
* if value is absent or null * if value is absent or null
*/ */
@ -172,35 +364,47 @@ public interface Config extends ConfigMergeable {
/** /**
* Gets the value at the given path, unless the value is a null value or * Gets the value at the given path, unless the value is a null value or
* missing, in which case it throws just like the other getters. Use get() * missing, in which case it throws just like the other getters. Use
* from the Map interface if you want an unprocessed value. * {@code get()} from the {@link java.util.Map Map} interface if you want an
* unprocessed value.
* *
* @param path * @param path
* @return * path expression
* @return the value at the requested path
* @throws ConfigException.Missing * @throws ConfigException.Missing
* if value is absent or null * if value is absent or null
*/ */
ConfigValue getValue(String path); ConfigValue getValue(String path);
/** /**
* Get value as a size in bytes (parses special strings like "128M"). The * Gets a value as a size in bytes (parses special strings like "128M"). If
* size units are interpreted as for memory, not as for disk space, so they * the value is already a number, then it's left alone; if it's a string,
* are in powers of two. * it's parsed understanding unit suffixes such as "128K", as documented in
* the <a href="https://github.com/havocp/config/blob/master/HOCON.md">the
* spec</a>.
* *
* @param path
* path expression
* @return the value at the requested path, in bytes
* @throws ConfigException.Missing * @throws ConfigException.Missing
* if value is absent or null * if value is absent or null
* @throws ConfigException.WrongType * @throws ConfigException.WrongType
* if value is not convertible to Long or String * if value is not convertible to Long or String
* @throws ConfigException.BadValue * @throws ConfigException.BadValue
* if value cannot be parsed as a memory size * if value cannot be parsed as a size in bytes
*/ */
Long getMemorySizeInBytes(String path); Long getBytes(String path);
/** /**
* Get value as a duration in milliseconds. If the value is already a * Get value as a duration in milliseconds. If the value is already a
* number, then it's left alone; if it's a string, it's parsed understanding * number, then it's left alone; if it's a string, it's parsed understanding
* units suffixes like "10m" or "5ns" * units suffixes like "10m" or "5ns" as documented in the <a
* href="https://github.com/havocp/config/blob/master/HOCON.md">the
* spec</a>.
* *
* @param path
* path expression
* @return the duration value at the requested path, in milliseconds
* @throws ConfigException.Missing * @throws ConfigException.Missing
* if value is absent or null * if value is absent or null
* @throws ConfigException.WrongType * @throws ConfigException.WrongType
@ -213,8 +417,12 @@ public interface Config extends ConfigMergeable {
/** /**
* Get value as a duration in nanoseconds. If the value is already a number * Get value as a duration in nanoseconds. If the value is already a number
* it's taken as milliseconds and converted to nanoseconds. If it's a * it's taken as milliseconds and converted to nanoseconds. If it's a
* string, it's parsed understanding unit suffixes. * string, it's parsed understanding unit suffixes, as for
* {@link #getMilliseconds(String)}.
* *
* @param path
* path expression
* @return the duration value at the requested path, in nanoseconds
* @throws ConfigException.Missing * @throws ConfigException.Missing
* if value is absent or null * if value is absent or null
* @throws ConfigException.WrongType * @throws ConfigException.WrongType
@ -225,13 +433,13 @@ public interface Config extends ConfigMergeable {
Long getNanoseconds(String path); Long getNanoseconds(String path);
/** /**
* Gets a list value (with any element type) as a ConfigList, which * Gets a list value (with any element type) as a {@link ConfigList}, which
* implements java.util.List<ConfigValue>. Throws if the path is unset or * implements {@code java.util.List<ConfigValue>}. Throws if the path is
* null. * unset or null.
* *
* @param path * @param path
* the path to the list value. * the path to the list value.
* @return the ConfigList at the path * @return the {@link ConfigList} at the path
* @throws ConfigException.Missing * @throws ConfigException.Missing
* if value is absent or null * if value is absent or null
* @throws ConfigException.WrongType * @throws ConfigException.WrongType

View file

@ -3,15 +3,19 @@
*/ */
package com.typesafe.config; package com.typesafe.config;
/** /**
* All exceptions thrown by the library are subclasses of ConfigException. * All exceptions thrown by the library are subclasses of ConfigException.
*/ */
public class ConfigException extends RuntimeException { public class ConfigException extends RuntimeException {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
final private ConfigOrigin origin;
protected ConfigException(ConfigOrigin origin, String message, protected ConfigException(ConfigOrigin origin, String message,
Throwable cause) { Throwable cause) {
super(origin.description() + ": " + message, cause); super(origin.description() + ": " + message, cause);
this.origin = origin;
} }
protected ConfigException(ConfigOrigin origin, String message) { protected ConfigException(ConfigOrigin origin, String message) {
@ -20,12 +24,26 @@ public class ConfigException extends RuntimeException {
protected ConfigException(String message, Throwable cause) { protected ConfigException(String message, Throwable cause) {
super(message, cause); super(message, cause);
this.origin = null;
} }
protected ConfigException(String message) { protected ConfigException(String message) {
this(message, null); this(message, null);
} }
/**
* Returns an "origin" (such as a filename and line number) for the
* exception, or null if none is available. If there's no sensible origin
* for a given exception, or the kind of exception doesn't meaningfully
* relate to a particular origin file, this returns null. Never assume this
* will return non-null, it can always return null.
*
* @return origin of the problem, or null if unknown/inapplicable
*/
public ConfigOrigin origin() {
return origin;
}
/** /**
* Exception indicating that the type of a value does not match the type you * Exception indicating that the type of a value does not match the type you
* requested. * requested.
@ -163,10 +181,11 @@ public class ConfigException extends RuntimeException {
} }
/** /**
* Exception indicating that there's a bug in something or the runtime * Exception indicating that there's a bug in something (possibly the
* environment is broken. This exception should never be handled; instead, * library itself) or the runtime environment is broken. This exception
* something should be fixed to keep the exception from occurring. * should never be handled; instead, something should be fixed to keep the
* * exception from occurring. This exception can be thrown by any method in
* the library.
*/ */
public static class BugOrBroken extends ConfigException { public static class BugOrBroken extends ConfigException {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@ -212,12 +231,29 @@ public class ConfigException extends RuntimeException {
} }
} }
/**
* Exception indicating that a substitution did not resolve to anything.
* Thrown by {@link Config#resolve}.
*/
public static class UnresolvedSubstitution extends Parse {
private static final long serialVersionUID = 1L;
public UnresolvedSubstitution(ConfigOrigin origin, String expression, Throwable cause) {
super(origin, "Could not resolve substitution to a value: " + expression, cause);
}
public UnresolvedSubstitution(ConfigOrigin origin, String expression) {
this(origin, expression, null);
}
}
/** /**
* Exception indicating that you tried to use a function that requires * Exception indicating that you tried to use a function that requires
* substitutions to be resolved, but substitutions have not been resolved. * substitutions to be resolved, but substitutions have not been resolved
* This is always a bug in either application code or the library; it's * (that is, {@link Config#resolve} was not called). This is always a bug in
* wrong to write a handler for this exception because you should be able to * either application code or the library; it's wrong to write a handler for
* fix the code to avoid it. * this exception because you should be able to fix the code to avoid it by
* adding calls to {@link Config#resolve}.
*/ */
public static class NotResolved extends BugOrBroken { public static class NotResolved extends BugOrBroken {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@ -230,4 +266,59 @@ public class ConfigException extends RuntimeException {
this(message, null); this(message, null);
} }
} }
public static class ValidationProblem {
final private String path;
final private ConfigOrigin origin;
final private String problem;
public ValidationProblem(String path, ConfigOrigin origin, String problem) {
this.path = path;
this.origin = origin;
this.problem = problem;
}
public String path() {
return path;
}
public ConfigOrigin origin() {
return origin;
}
public String problem() {
return problem;
}
}
public static class ValidationFailed extends ConfigException {
private static final long serialVersionUID = 1L;
final private Iterable<ValidationProblem> problems;
public ValidationFailed(Iterable<ValidationProblem> problems) {
super(makeMessage(problems), null);
this.problems = problems;
}
public Iterable<ValidationProblem> problems() {
return problems;
}
private static String makeMessage(Iterable<ValidationProblem> problems) {
StringBuilder sb = new StringBuilder();
for (ValidationProblem p : problems) {
sb.append(p.origin().description());
sb.append(": ");
sb.append(p.path());
sb.append(": ");
sb.append(p.problem());
sb.append(", ");
}
sb.setLength(sb.length() - 2); // chop comma and space
return sb.toString();
}
}
} }

View file

@ -13,203 +13,430 @@ import com.typesafe.config.impl.ConfigImpl;
import com.typesafe.config.impl.Parseable; import com.typesafe.config.impl.Parseable;
/** /**
* This class contains static methods for creating Config objects. * Contains static methods for creating {@link Config} instances.
* *
* <p>
* See also {@link ConfigValueFactory} which contains static methods for
* converting Java values into a {@link ConfigObject}. You can then convert a
* {@code ConfigObject} into a {@code Config} with {@link ConfigObject#toConfig}.
*
* <p>
* The static methods with "load" in the name do some sort of higher-level * The static methods with "load" in the name do some sort of higher-level
* operation potentially parsing multiple resources and resolving substitutions, * operation potentially parsing multiple resources and resolving substitutions,
* while the ones with "parse" in the name just create a ConfigValue from a * while the ones with "parse" in the name just create a {@link ConfigValue}
* resource and nothing else. * from a resource and nothing else.
*/ */
public final class ConfigFactory { public final class ConfigFactory {
private ConfigFactory() {
}
/** /**
* Loads a configuration for the given root path in a "standard" way. * Loads an application's configuration from the given classpath resource or
* Oversimplified, if your root path is foo.bar then this will load files * classpath resource basename, sandwiches it between default reference
* from the classpath: foo-bar.conf, foo-bar.json, foo-bar.properties, * config and default overrides, and then resolves it. The classpath
* foo-bar-reference.conf, foo-bar-reference.json, * resource is "raw" (it should have no "/" prefix, and is not made relative
* foo-bar-reference.properties. It will override all those files with any * to any package, so it's like {@link ClassLoader#getResource} not
* system properties that begin with "foo.bar.", as well. * {@link Class#getResource}).
*
* The root path should be a path expression, usually just a single short
* word, that scopes the package being configured; typically it's the
* package name or something similar. System properties overriding values in
* the configuration will have to be prefixed with the root path. The root
* path may have periods in it if you like but other punctuation or
* whitespace will probably cause you headaches. Example root paths: "akka",
* "sbt", "jsoup", "heroku", "mongo", etc.
* *
* <p>
* The loaded object will already be resolved (substitutions have already * The loaded object will already be resolved (substitutions have already
* been processed). As a result, if you add more fallbacks then they won't * been processed). As a result, if you add more fallbacks then they won't
* be seen by substitutions. Substitutions are the "${foo.bar}" syntax. If * be seen by substitutions. Substitutions are the "${foo.bar}" syntax. If
* you want to parse additional files or something then you need to use * you want to parse additional files or something then you need to use
* loadWithoutResolving(). * {@link #load(Config)}.
* *
* @param rootPath * @param resourceBasename
* the configuration "domain" * name (optionally without extension) of a resource on classpath
* @return configuration object for the requested root path * @return configuration for an application
*/ */
public static ConfigRoot load(String rootPath) { public static Config load(String resourceBasename) {
return loadWithoutResolving(rootPath).resolve(); return load(resourceBasename, ConfigParseOptions.defaults(),
} ConfigResolveOptions.defaults());
public static ConfigRoot load(String rootPath,
ConfigParseOptions parseOptions, ConfigResolveOptions resolveOptions) {
return loadWithoutResolving(rootPath, parseOptions).resolve(
resolveOptions);
} }
/** /**
* Like load() but does not resolve the object, so you can go ahead and add * Like {@link #load(String)} but allows you to specify parse and resolve
* more fallbacks and stuff and have them seen by substitutions when you do * options.
* call {@link ConfigRoot.resolve()}.
* *
* @param rootPath * <p>
* @return * To be aware of: using
* {@link ConfigResolveOptions#setUseSystemProperties(boolean)
* setUseSystemProperties(false)} with this method has no meaningful effect,
* because the system properties are merged into the config as overrides
* anyway. <code>setUseSystemProperties</code> affects whether to fall back
* to system properties when they are not found in the config, but with
* <code>load()</code>, they will be in the config.
*
* @param resourceBasename
* the classpath resource name with optional extension
* @param parseOptions
* options to use when parsing the resource
* @param resolveOptions
* options to use when resolving the stack
* @return configuration for an application
*/ */
public static ConfigRoot loadWithoutResolving(String rootPath) { public static Config load(String resourceBasename, ConfigParseOptions parseOptions,
return loadWithoutResolving(rootPath, ConfigParseOptions.defaults()); ConfigResolveOptions resolveOptions) {
Config appConfig = ConfigFactory.parseResourcesAnySyntax(ConfigFactory.class, "/"
+ resourceBasename, parseOptions);
return load(appConfig, resolveOptions);
} }
public static ConfigRoot loadWithoutResolving(String rootPath, /**
ConfigParseOptions options) { * Assembles a standard configuration using a custom <code>Config</code>
ConfigRoot system = systemPropertiesRoot(rootPath); * object rather than loading "application.conf". The <code>Config</code>
* object will be sandwiched between the default reference config and
Config mainFiles = parseResourcesForPath(rootPath, options); * default overrides and then resolved.
Config referenceFiles = parseResourcesForPath(rootPath + ".reference", *
options); * @param config
* the application's portion of the configuration
return system.withFallback(mainFiles).withFallback(referenceFiles); * @return resolved configuration with overrides and fallbacks added
*/
public static Config load(Config config) {
return load(config, ConfigResolveOptions.defaults());
} }
public static ConfigRoot emptyRoot(String rootPath) { /**
return emptyRoot(rootPath, null); * Like {@link #load(Config)} but allows you to specify
* {@link ConfigResolveOptions}.
*
* <p>
* To be aware of: using
* {@link ConfigResolveOptions#setUseSystemProperties(boolean)
* setUseSystemProperties(false)} with this method has no meaningful effect,
* because the system properties are merged into the config as overrides
* anyway. <code>setUseSystemProperties</code> affects whether to fall back
* to system properties when they are not found in the config, but with
* <code>load()</code>, they will be in the config.
*
* @param config
* the application's portion of the configuration
* @param resolveOptions
* options for resolving the assembled config stack
* @return resolved configuration with overrides and fallbacks added
*/
public static Config load(Config config, ConfigResolveOptions resolveOptions) {
return defaultOverrides().withFallback(config).withFallback(defaultReference())
.resolve(resolveOptions);
} }
private static class DefaultConfigHolder {
static final Config defaultConfig = load("application");
}
/**
* Loads a default configuration, equivalent to {@link #load(String)
* load("application")}. This configuration should be used by libraries and
* frameworks unless an application provides a different one.
* <p>
* This method may return a cached singleton.
*
* @return configuration for an application
*/
public static Config load() {
return DefaultConfigHolder.defaultConfig;
}
/**
* Obtains the default reference configuration, which is currently created
* by merging all resources "reference.conf" found on the classpath and
* overriding the result with system properties. The returned reference
* configuration will already have substitutions resolved.
*
* <p>
* Libraries and frameworks should ship with a "reference.conf" in their
* jar.
*
* <p>
* The {@link #load()} methods merge this configuration for you
* automatically.
*
* <p>
* Future versions may look for reference configuration in more places. It
* is not guaranteed that this method <em>only</em> looks at
* "reference.conf".
*
* @return the default reference config
*/
public static Config defaultReference() {
return ConfigImpl.defaultReference();
}
/**
* Obtains the default override configuration, which currently consists of
* system properties. The returned override configuration will already have
* substitutions resolved.
*
* <p>
* The {@link #load()} methods merge this configuration for you
* automatically.
*
* <p>
* Future versions may get overrides in more places. It is not guaranteed
* that this method <em>only</em> uses system properties.
*
* @return the default override configuration
*/
public static Config defaultOverrides() {
return systemProperties();
}
/**
* Gets an empty configuration. See also {@link #empty(String)} to create an
* empty configuration with a description, which may improve user-visible
* error messages.
*
* @return an empty configuration
*/
public static Config empty() { public static Config empty() {
return empty(null); return empty(null);
} }
public static ConfigRoot emptyRoot(String rootPath, String originDescription) { /**
return ConfigImpl.emptyRoot(rootPath, originDescription); * Gets an empty configuration with a description to be used to create a
} * {@link ConfigOrigin} for this <code>Config</code>. The description should
* be very short and say what the configuration is, like "default settings"
* or "foo settings" or something. (Presumably you will merge some actual
* settings into this empty config using {@link Config#withFallback}, making
* the description more useful.)
*
* @param originDescription
* description of the config
* @return an empty configuration
*/
public static Config empty(String originDescription) { public static Config empty(String originDescription) {
return ConfigImpl.emptyConfig(originDescription); return ConfigImpl.emptyConfig(originDescription);
} }
public static ConfigRoot systemPropertiesRoot(String rootPath) { /**
return ConfigImpl.systemPropertiesRoot(rootPath); * Gets a <code>Config</code> containing the system properties from
} * {@link java.lang.System#getProperties()}, parsed and converted as with
* {@link #parseProperties}. This method can return a global immutable
* singleton, so it's preferred over parsing system properties yourself.
*
* <p>
* {@link #load} will include the system properties as overrides already, as
* will {@link #defaultReference} and {@link #defaultOverrides}.
*
* <p>
* Because this returns a singleton, it will not notice changes to system
* properties made after the first time this method is called.
*
* @return system properties parsed into a <code>Config</code>
*/
public static Config systemProperties() { public static Config systemProperties() {
return ConfigImpl.systemPropertiesAsConfig(); return ConfigImpl.systemPropertiesAsConfig();
} }
/**
* Gets a <code>Config</code> containing the system's environment variables.
* This method can return a global immutable singleton.
*
* <p>
* Environment variables are used as fallbacks when resolving substitutions
* whether or not this object is included in the config being resolved, so
* you probably don't need to use this method for most purposes. It can be a
* nicer API for accessing environment variables than raw
* {@link java.lang.System#getenv(String)} though, since you can use methods
* such as {@link Config#getInt}.
*
* @return system environment variables parsed into a <code>Config</code>
*/
public static Config systemEnvironment() { public static Config systemEnvironment() {
return ConfigImpl.envVariablesAsConfig(); return ConfigImpl.envVariablesAsConfig();
} }
/** /**
* Converts a Java Properties object to a ConfigObject using the rules * Converts a Java {@link java.util.Properties} object to a
* documented in https://github.com/havocp/config/blob/master/HOCON.md The * {@link ConfigObject} using the rules documented in the <a
* keys in the Properties object are split on the period character '.' and * href="https://github.com/havocp/config/blob/master/HOCON.md">HOCON
* treated as paths. The values will all end up as string values. If you * spec</a>. The keys in the <code>Properties</code> object are split on the
* have both "a=foo" and "a.b=bar" in your properties file, so "a" is both * period character '.' and treated as paths. The values will all end up as
* the object containing "b" and the string "foo", then the string value is * string values. If you have both "a=foo" and "a.b=bar" in your properties
* dropped. * file, so "a" is both the object containing "b" and the string "foo", then
* the string value is dropped.
* *
* If you want to get System.getProperties() as a ConfigObject, it's better * <p>
* to use the systemProperties() or systemPropertiesRoot() methods. Those * If you want to have <code>System.getProperties()</code> as a
* methods are able to use a cached global singleton ConfigObject for the * ConfigObject, it's better to use the {@link #systemProperties()} method
* system properties. * which returns a cached global singleton.
* *
* @param properties * @param properties
* a Java Properties object * a Java Properties object
* @param options * @param options
* @return * @return the parsed configuration
*/ */
public static Config parseProperties(Properties properties, public static Config parseProperties(Properties properties,
ConfigParseOptions options) { ConfigParseOptions options) {
return Parseable.newProperties(properties, options).parse().toConfig(); return Parseable.newProperties(properties, options).parse().toConfig();
} }
public static Config parseProperties(Properties properties) {
return parseProperties(properties, ConfigParseOptions.defaults());
}
public static Config parseReader(Reader reader, ConfigParseOptions options) { public static Config parseReader(Reader reader, ConfigParseOptions options) {
return Parseable.newReader(reader, options).parse().toConfig(); return Parseable.newReader(reader, options).parse().toConfig();
} }
public static Config parseReader(Reader reader) {
return parseReader(reader, ConfigParseOptions.defaults());
}
public static Config parseURL(URL url, ConfigParseOptions options) { public static Config parseURL(URL url, ConfigParseOptions options) {
return Parseable.newURL(url, options).parse().toConfig(); return Parseable.newURL(url, options).parse().toConfig();
} }
public static Config parseURL(URL url) {
return parseURL(url, ConfigParseOptions.defaults());
}
public static Config parseFile(File file, ConfigParseOptions options) { public static Config parseFile(File file, ConfigParseOptions options) {
return Parseable.newFile(file, options).parse().toConfig(); return Parseable.newFile(file, options).parse().toConfig();
} }
public static Config parseFile(File file) {
return parseFile(file, ConfigParseOptions.defaults());
}
/** /**
* Parses a file. If the fileBasename already ends in a known extension, * Parses a file with a flexible extension. If the <code>fileBasename</code>
* just parses it according to that extension. If the fileBasename does not * already ends in a known extension, this method parses it according to
* end in an extension, then parse all known extensions and merge whatever * that extension (the file's syntax must match its extension). If the
* is found. If options force a specific syntax, only parse files with an * <code>fileBasename</code> does not end in an extension, it parses files
* extension matching that syntax. If options.getAllowMissing() is true, * with all known extensions and merges whatever is found.
* then no files have to exist; if false, then at least one file has to *
* exist. * <p>
* In the current implementation, the extension ".conf" forces
* {@link ConfigSyntax#CONF}, ".json" forces {@link ConfigSyntax#JSON}, and
* ".properties" forces {@link ConfigSyntax#PROPERTIES}. When merging files,
* ".conf" falls back to ".json" falls back to ".properties".
*
* <p>
* Future versions of the implementation may add additional syntaxes or
* additional extensions. However, the ordering (fallback priority) of the
* three current extensions will remain the same.
*
* <p>
* If <code>options</code> forces a specific syntax, this method only parses
* files with an extension matching that syntax.
*
* <p>
* If {@link ConfigParseOptions#getAllowMissing options.getAllowMissing()}
* is true, then no files have to exist; if false, then at least one file
* has to exist.
* *
* @param fileBasename * @param fileBasename
* a filename with or without extension
* @param options * @param options
* @return * parse options
* @return the parsed configuration
*/ */
public static Config parseFileAnySyntax(File fileBasename, public static Config parseFileAnySyntax(File fileBasename,
ConfigParseOptions options) { ConfigParseOptions options) {
return ConfigImpl.parseFileAnySyntax(fileBasename, options).toConfig(); return ConfigImpl.parseFileAnySyntax(fileBasename, options).toConfig();
} }
public static Config parseResource(Class<?> klass, String resource, public static Config parseFileAnySyntax(File fileBasename) {
ConfigParseOptions options) { return parseFileAnySyntax(fileBasename, ConfigParseOptions.defaults());
return Parseable.newResource(klass, resource, options).parse()
.toConfig();
} }
/** /**
* Same behavior as parseFileAnySyntax() but for classpath resources * Parses all resources on the classpath with the given name and merges them
* instead. * into a single <code>Config</code>.
*
* <p>
* If the resource name does not begin with a "/", it will have the supplied
* class's package added to it, in the same way as
* {@link java.lang.Class#getResource}.
*
* <p>
* Duplicate resources with the same name are merged such that ones returned
* earlier from {@link ClassLoader#getResources} fall back to (have higher
* priority than) the ones returned later. This implies that resources
* earlier in the classpath override those later in the classpath when they
* configure the same setting. However, in practice real applications may
* not be consistent about classpath ordering, so be careful. It may be best
* to avoid assuming too much.
* *
* @param klass * @param klass
* @param resourceBasename * <code>klass.getClassLoader()</code> will be used to load
* resources, and non-absolute resource names will have this
* class's package added
* @param resource
* resource to look up, relative to <code>klass</code>'s package
* or absolute starting with a "/"
* @param options * @param options
* @return * parse options
* @return the parsed configuration
*/ */
public static Config parseResourceAnySyntax(Class<?> klass, String resourceBasename, public static Config parseResources(Class<?> klass, String resource,
ConfigParseOptions options) { ConfigParseOptions options) {
return ConfigImpl.parseResourceAnySyntax(klass, resourceBasename, return Parseable.newResources(klass, resource, options).parse()
.toConfig();
}
public static Config parseResources(Class<?> klass, String resource) {
return parseResources(klass, resource, ConfigParseOptions.defaults());
}
/**
* Parses classpath resources with a flexible extension. In general, this
* method has the same behavior as
* {@link #parseFileAnySyntax(File,ConfigParseOptions)} but for classpath
* resources instead, as in {@link #parseResources}.
*
* <p>
* There is a thorny problem with this method, which is that
* {@link java.lang.ClassLoader#getResources} must be called separately for
* each possible extension. The implementation ends up with separate lists
* of resources called "basename.conf" and "basename.json" for example. As a
* result, the ideal ordering between two files with different extensions is
* unknown; there is no way to figure out how to merge the two lists in
* classpath order. To keep it simple, the lists are simply concatenated,
* with the same syntax priorities as
* {@link #parseFileAnySyntax(File,ConfigParseOptions) parseFileAnySyntax()}
* - all ".conf" resources are ahead of all ".json" resources which are
* ahead of all ".properties" resources.
*
* @param klass
* class which determines the <code>ClassLoader</code> and the
* package for relative resource names
* @param resourceBasename
* a resource name as in {@link java.lang.Class#getResource},
* with or without extension
* @param options
* parse options
* @return the parsed configuration
*/
public static Config parseResourcesAnySyntax(Class<?> klass, String resourceBasename,
ConfigParseOptions options) {
return ConfigImpl.parseResourcesAnySyntax(klass, resourceBasename,
options).toConfig(); options).toConfig();
} }
public static Config parseResourcesAnySyntax(Class<?> klass, String resourceBasename) {
return parseResourcesAnySyntax(klass, resourceBasename, ConfigParseOptions.defaults());
}
public static Config parseString(String s, ConfigParseOptions options) { public static Config parseString(String s, ConfigParseOptions options) {
return Parseable.newString(s, options).parse().toConfig(); return Parseable.newString(s, options).parse().toConfig();
} }
/** public static Config parseString(String s) {
* Parses classpath resources corresponding to this path expression. return parseString(s, ConfigParseOptions.defaults());
* Essentially if the path is "foo.bar" then the resources are
* "/foo-bar.conf", "/foo-bar.json", and "/foo-bar.properties". If more than
* one of those exists, they are merged.
*
* @param path
* @param options
* @return
*/
public static Config parseResourcesForPath(String rootPath,
ConfigParseOptions options) {
// null originDescription is allowed in parseResourcesForPath
return ConfigImpl.parseResourcesForPath(rootPath, options).toConfig();
} }
/** /**
* Similar to ConfigValueFactory.fromMap(), but the keys in the map are path * Creates a {@code Config} based on a {@link java.util.Map} from paths to
* expressions, rather than keys; and correspondingly it returns a Config * plain Java values. Similar to
* instead of a ConfigObject. This is more convenient if you are writing * {@link ConfigValueFactory#fromMap(Map,String)}, except the keys in the
* literal maps in code, and less convenient if you are getting your maps * map are path expressions, rather than keys; and correspondingly it
* from some data source such as a parser. * returns a {@code Config} instead of a {@code ConfigObject}. This is more
* convenient if you are writing literal maps in code, and less convenient
* if you are getting your maps from some data source such as a parser.
* *
* <p>
* An exception will be thrown (and it is a bug in the caller of the method) * An exception will be thrown (and it is a bug in the caller of the method)
* if a path is both an object and a value, for example if you had both * if a path is both an object and a value, for example if you had both
* "a=foo" and "a.b=bar", then "a" is both the string "foo" and the parent * "a=foo" and "a.b=bar", then "a" is both the string "foo" and the parent
@ -221,7 +448,7 @@ public final class ConfigFactory {
* description of what this map represents, like a filename, or * description of what this map represents, like a filename, or
* "default settings" (origin description is used in error * "default settings" (origin description is used in error
* messages) * messages)
* @return * @return the map converted to a {@code Config}
*/ */
public static Config parseMap(Map<String, ? extends Object> values, public static Config parseMap(Map<String, ? extends Object> values,
String originDescription) { String originDescription) {
@ -229,11 +456,11 @@ public final class ConfigFactory {
} }
/** /**
* See the other overload of parseMap() for details, this one just uses a * See the other overload of {@link #parseMap(Map, String)} for details,
* default origin description. * this one just uses a default origin description.
* *
* @param values * @param values
* @return * @return the map converted to a {@code Config}
*/ */
public static Config parseMap(Map<String, ? extends Object> values) { public static Config parseMap(Map<String, ? extends Object> values) {
return parseMap(values, null); return parseMap(values, null);

View file

@ -5,8 +5,9 @@ package com.typesafe.config;
/** /**
* A ConfigIncludeContext is passed to a ConfigIncluder. This interface is not * Context provided to a {@link ConfigIncluder}; this interface is only useful
* intended for apps to implement. * inside a {@code ConfigIncluder} implementation, and is not intended for apps
* to implement.
*/ */
public interface ConfigIncludeContext { public interface ConfigIncludeContext {
/** /**
@ -15,7 +16,7 @@ public interface ConfigIncludeContext {
* null if it can't meaningfully create a relative name. The returned * null if it can't meaningfully create a relative name. The returned
* parseable may not exist; this function is not required to do any IO, just * parseable may not exist; this function is not required to do any IO, just
* compute what the name would be. * compute what the name would be.
* *
* The passed-in filename has to be a complete name (with extension), not * The passed-in filename has to be a complete name (with extension), not
* just a basename. (Include statements in config files are allowed to give * just a basename. (Include statements in config files are allowed to give
* just a basename.) * just a basename.)

View file

@ -4,8 +4,9 @@
package com.typesafe.config; package com.typesafe.config;
/** /**
* Interface you have to implement to customize "include" statements in config * Implement this interface and provide an instance to
* files. * {@link ConfigParseOptions#setIncluder ConfigParseOptions.setIncluder()} to
* customize handling of {@code include} statements in config files.
*/ */
public interface ConfigIncluder { public interface ConfigIncluder {
/** /**
@ -29,7 +30,7 @@ public interface ConfigIncluder {
* Parses another item to be included. The returned object typically would * Parses another item to be included. The returned object typically would
* not have substitutions resolved. You can throw a ConfigException here to * not have substitutions resolved. You can throw a ConfigException here to
* abort parsing, or return an empty object, but may not return null. * abort parsing, or return an empty object, but may not return null.
* *
* @param context * @param context
* some info about the include context * some info about the include context
* @param what * @param what

View file

@ -6,9 +6,30 @@ package com.typesafe.config;
import java.util.List; import java.util.List;
/** /**
* A list (aka array) value corresponding to ConfigValueType.LIST or JSON's * Subtype of {@link ConfigValue} representing a list value, as in JSON's
* "[1,2,3]" value. Implements java.util.List<ConfigValue> so you can use it * {@code [1,2,3]} syntax.
* like a regular Java list. *
* <p>
* {@code ConfigList} implements {@code java.util.List<ConfigValue>} so you can
* use it like a regular Java list. Or call {@link #unwrapped()} to unwrap the
* list elements into plain Java values.
*
* <p>
* Like all {@link ConfigValue} subtypes, {@code ConfigList} is immutable. This
* makes it threadsafe and you never have to create "defensive copies." The
* mutator methods from {@link java.util.List} all throw
* {@link java.lang.UnsupportedOperationException}.
*
* <p>
* The {@link ConfigValue#valueType} method on a list returns
* {@link ConfigValueType#LIST}.
*
* <p>
* <em>Do not implement {@code ConfigList}</em>; it should only be implemented
* by the config library. Arbitrary implementations will not work because the
* library internals assume a specific concrete implementation. Also, this
* interface is likely to grow new methods over time, so third-party
* implementations will break.
* *
*/ */
public interface ConfigList extends List<ConfigValue>, ConfigValue { public interface ConfigList extends List<ConfigValue>, ConfigValue {

View file

@ -4,33 +4,48 @@
package com.typesafe.config; package com.typesafe.config;
/** /**
* This is a marker for types that can be merged as a fallback into a Config or * Marker for types whose instances can be merged, that is {@link Config} and
* a ConfigValue. Both Config and ConfigValue are mergeable. * {@link ConfigValue}. Instances of {@code Config} and {@code ConfigValue} can
* be combined into a single new instance using the
* {@link ConfigMergeable#withFallback withFallback()} method.
*
* <p>
* <em>Do not implement this interface</em>; it should only be implemented by
* the config library. Arbitrary implementations will not work because the
* library internals assume a specific concrete implementation. Also, this
* interface is likely to grow new methods over time, so third-party
* implementations will break.
*/ */
public interface ConfigMergeable { public interface ConfigMergeable {
/** /**
* Converts the mergeable to a ConfigValue to be merged. * Converts this instance to a {@link ConfigValue}. If called on a
* {@code ConfigValue} it returns {@code this}, if called on a
* {@link Config} it's equivalent to {@link Config#root()}.
* *
* @return * @return this instance as a {@code ConfigValue}
*/ */
ConfigValue toValue(); ConfigValue toValue();
/** /**
* Returns a new value computed by merging this value with another, with * Returns a new value computed by merging this value with another, with
* keys in this value "winning" over the other one. Only ConfigObject and * keys in this value "winning" over the other one. Only
* Config instances do anything in this method (they need to merge the * {@link ConfigObject} and {@link Config} instances do anything in this
* fallback keys into themselves). All other values just return the original * method (they need to merge the fallback keys into themselves). All other
* value, since they automatically override any fallback. * values just return the original value, since they automatically override
* * any fallback.
* The semantics of merging are described in *
* https://github.com/havocp/config/blob/master/HOCON.md * <p>
* * The semantics of merging are described in the <a
* Note that objects do not merge "across" non-objects; if you do * href="https://github.com/havocp/config/blob/master/HOCON.md">spec for
* HOCON</a>.
*
* <p>
* Note that objects do not merge "across" non-objects; if you write
* <code>object.withFallback(nonObject).withFallback(otherObject)</code>, * <code>object.withFallback(nonObject).withFallback(otherObject)</code>,
* then <code>otherObject</code> will simply be ignored. This is an * then <code>otherObject</code> will simply be ignored. This is an
* intentional part of how merging works. Both non-objects, and any object * intentional part of how merging works. Both non-objects, and any object
* which has fallen back to a non-object, block subsequent fallbacks. * which has fallen back to a non-object, block subsequent fallbacks.
* *
* @param other * @param other
* an object whose keys should be used if the keys are not * an object whose keys should be used if the keys are not
* present in this one * present in this one

View file

@ -6,54 +6,67 @@ package com.typesafe.config;
import java.util.Map; import java.util.Map;
/** /**
* A ConfigObject is a read-only configuration object, which may have nested * Subtype of {@link ConfigValue} representing an object (dictionary, map)
* child objects. Implementations of ConfigObject should be immutable (at least * value, as in JSON's <code>{ "a" : 42 }</code> syntax.
* from the perspective of anyone using this interface) and thus thread-safe.
* *
* In most cases you want to use the Config interface rather than this one. Call * <p>
* toConfig() to convert a ConfigObject to a config. * {@code ConfigObject} implements {@code java.util.Map<String, ConfigValue>} so
* you can use it like a regular Java map. Or call {@link #unwrapped()} to
* unwrap the map to a map with plain Java values rather than
* {@code ConfigValue}.
* *
* The API for a ConfigObject is in terms of keys, while the API for a Config is * <p>
* in terms of path expressions. Conceptually, ConfigObject is a tree of maps * Like all {@link ConfigValue} subtypes, {@code ConfigObject} is immutable.
* from keys to values, while a ConfigObject is a one-level map from paths to * This makes it threadsafe and you never have to create "defensive copies." The
* values. * mutator methods from {@link java.util.Map} all throw
* {@link java.lang.UnsupportedOperationException}.
* *
* Throughout the API, there is a distinction between "keys" and "paths". A key * <p>
* is a key in a JSON object; it's just a string that's the key in a map. A * The {@link ConfigValue#valueType} method on an object returns
* "path" is a parseable expression with a syntax and it refers to a series of * {@link ConfigValueType#OBJECT}.
* keys. A path is used to traverse nested ConfigObject by looking up each key
* in the path. Path expressions are described in the spec for "HOCON", which
* can be found at https://github.com/havocp/config/blob/master/HOCON.md; in
* brief, a path is period-separated so "a.b.c" looks for key c in object b in
* object a in the root object. Sometimes double quotes are needed around
* special characters in path expressions.
* *
* ConfigObject implements java.util.Map<String,ConfigValue> and all methods * <p>
* work with keys, not path expressions. * In most cases you want to use the {@link Config} interface rather than this
* one. Call {@link #toConfig()} to convert a {@code ConfigObject} to a
* {@code Config}.
* *
* While ConfigObject implements the standard Java Map interface, the mutator * <p>
* methods all throw UnsupportedOperationException. This Map is immutable. * The API for a {@code ConfigObject} is in terms of keys, while the API for a
* {@link Config} is in terms of path expressions. Conceptually,
* {@code ConfigObject} is a tree of maps from keys to values, while a
* {@code ConfigObject} is a one-level map from paths to values.
* *
* The Map may contain null values, which will have ConfigValue.valueType() == * <p>
* ConfigValueType.NULL. If get() returns Java's null then the key was not * A {@code ConfigObject} may contain null values, which will have
* present in the parsed file (or wherever this value tree came from). If get() * {@link ConfigValue#valueType()} equal to {@link ConfigValueType#NULL}. If
* returns a ConfigValue with type ConfigValueType.NULL then the key was set to * {@code get()} returns Java's null then the key was not present in the parsed
* null explicitly. * file (or wherever this value tree came from). If {@code get()} returns a
* {@link ConfigValue} with type {@code ConfigValueType#NULL} then the key was
* set to null explicitly in the config file.
*
* <p>
* <em>Do not implement {@code ConfigObject}</em>; it should only be implemented
* by the config library. Arbitrary implementations will not work because the
* library internals assume a specific concrete implementation. Also, this
* interface is likely to grow new methods over time, so third-party
* implementations will break.
*/ */
public interface ConfigObject extends ConfigValue, Map<String, ConfigValue> { public interface ConfigObject extends ConfigValue, Map<String, ConfigValue> {
/** /**
* Converts this object to a Config instance, enabling you to use path * Converts this object to a {@link Config} instance, enabling you to use
* expressions to find values in the object. This is a constant-time * path expressions to find values in the object. This is a constant-time
* operation (it is not proportional to the size of the object). * operation (it is not proportional to the size of the object).
* *
* @return * @return a {@link Config} with this object as its root
*/ */
Config toConfig(); Config toConfig();
/** /**
* Recursively unwraps the object, returning a map from String to whatever * Recursively unwraps the object, returning a map from String to whatever
* plain Java values are unwrapped from the object's values. * plain Java values are unwrapped from the object's values.
*
* @return a {@link java.util.Map} containing plain Java objects
*/ */
@Override @Override
Map<String, Object> unwrapped(); Map<String, Object> unwrapped();
@ -62,10 +75,15 @@ public interface ConfigObject extends ConfigValue, Map<String, ConfigValue> {
ConfigObject withFallback(ConfigMergeable other); ConfigObject withFallback(ConfigMergeable other);
/** /**
* Gets a ConfigValue at the given key, or returns null if there is no * Gets a {@link ConfigValue} at the given key, or returns null if there is
* value. The returned ConfigValue may have ConfigValueType.NULL or any * no value. The returned {@link ConfigValue} may have
* other type, and the passed-in key must be a key in this object, rather * {@link ConfigValueType#NULL} or any other type, and the passed-in key
* than a path expression. * must be a key in this object, rather than a path expression.
*
* @param key
* key to look up
*
* @return the value at the key or null if none
*/ */
@Override @Override
ConfigValue get(Object key); ConfigValue get(Object key);

View file

@ -3,10 +3,67 @@
*/ */
package com.typesafe.config; package com.typesafe.config;
import java.net.URL;
/** /**
* ConfigOrigin is used to track the origin (such as filename and line number) * Represents the origin (such as filename and line number) of a
* of a ConfigValue or other object. The origin is used in error messages. * {@link ConfigValue} for use in error messages. Obtain the origin of a value
* with {@link ConfigValue#origin}. Exceptions may have an origin, see
* {@link ConfigException#origin}, but be careful because
* <code>ConfigException.origin()</code> may return null.
*
* <p>
* It's best to use this interface only for debugging; its accuracy is
* "best effort" rather than guaranteed, and a potentially-noticeable amount of
* memory could probably be saved if origins were not kept around, so in the
* future there might be some option to discard origins.
*
* <p>
* <em>Do not implement this interface</em>; it should only be implemented by
* the config library. Arbitrary implementations will not work because the
* library internals assume a specific concrete implementation. Also, this
* interface is likely to grow new methods over time, so third-party
* implementations will break.
*/ */
public interface ConfigOrigin { public interface ConfigOrigin {
/**
* Returns a string describing the origin of a value or exception. This will
* never return null.
*
* @return string describing the origin
*/
public String description(); public String description();
/**
* Returns a filename describing the origin. This will return null if the
* origin was not a file.
*
* @return filename of the origin or null
*/
public String filename();
/**
* Returns a URL describing the origin. This will return null if the origin
* has no meaningful URL.
*
* @return url of the origin or null
*/
public URL url();
/**
* Returns a classpath resource name describing the origin. This will return
* null if the origin was not a classpath resource.
*
* @return resource name of the origin or null
*/
public String resource();
/**
* Returns a line number where the value or exception originated. This will
* return -1 if there's no meaningful line number.
*
* @return line number or -1 if none is available
*/
public int lineNumber();
} }

View file

@ -4,6 +4,22 @@
package com.typesafe.config; package com.typesafe.config;
/**
* A set of options related to parsing.
*
* <p>
* This object is immutable, so the "setters" return a new object.
*
* <p>
* Here is an example of creating a custom {@code ConfigParseOptions}:
*
* <pre>
* ConfigParseOptions options = ConfigParseOptions.defaults()
* .setSyntax(ConfigSyntax.JSON)
* .setAllowMissing(false)
* </pre>
*
*/
public final class ConfigParseOptions { public final class ConfigParseOptions {
final ConfigSyntax syntax; final ConfigSyntax syntax;
final String originDescription; final String originDescription;
@ -24,10 +40,11 @@ public final class ConfigParseOptions {
/** /**
* Set the file format. If set to null, try to guess from any available * Set the file format. If set to null, try to guess from any available
* filename extension; if guessing fails, assume ConfigSyntax.CONF. * filename extension; if guessing fails, assume {@link ConfigSyntax#CONF}.
* *
* @param syntax * @param syntax
* @return * a syntax or {@code null} for best guess
* @return options with the syntax set
*/ */
public ConfigParseOptions setSyntax(ConfigSyntax syntax) { public ConfigParseOptions setSyntax(ConfigSyntax syntax) {
if (this.syntax == syntax) if (this.syntax == syntax)
@ -45,10 +62,11 @@ public final class ConfigParseOptions {
* Set a description for the thing being parsed. In most cases this will be * Set a description for the thing being parsed. In most cases this will be
* set up for you to something like the filename, but if you provide just an * set up for you to something like the filename, but if you provide just an
* input stream you might want to improve on it. Set to null to allow the * input stream you might want to improve on it. Set to null to allow the
* library to come up with something automatically. * library to come up with something automatically. This description is the
* basis for the {@link ConfigOrigin} of the parsed values.
* *
* @param originDescription * @param originDescription
* @return * @return options with the origin description set
*/ */
public ConfigParseOptions setOriginDescription(String originDescription) { public ConfigParseOptions setOriginDescription(String originDescription) {
if (this.originDescription == originDescription) if (this.originDescription == originDescription)
@ -79,7 +97,7 @@ public final class ConfigParseOptions {
* case. * case.
* *
* @param allowMissing * @param allowMissing
* @return * @return options with the "allow missing" flag set
*/ */
public ConfigParseOptions setAllowMissing(boolean allowMissing) { public ConfigParseOptions setAllowMissing(boolean allowMissing) {
if (this.allowMissing == allowMissing) if (this.allowMissing == allowMissing)

View file

@ -3,21 +3,40 @@
*/ */
package com.typesafe.config; package com.typesafe.config;
import java.net.URL;
/** An opaque handle to something that can be parsed. */ /**
* An opaque handle to something that can be parsed, obtained from
* {@link ConfigIncludeContext}.
*
* <p>
* <em>Do not implement this interface</em>; it should only be implemented by
* the config library. Arbitrary implementations will not work because the
* library internals assume a specific concrete implementation. Also, this
* interface is likely to grow new methods over time, so third-party
* implementations will break.
*/
public interface ConfigParseable { public interface ConfigParseable {
/** /**
* Parse whatever it is. * Parse whatever it is. The options should come from
* * {@link ConfigParseable#options options()} but you could tweak them if you
* like.
*
* @param options * @param options
* parse options, should be based on the ones from options() * parse options, should be based on the ones from
* {@link ConfigParseable#options options()}
*/ */
ConfigObject parse(ConfigParseOptions options); ConfigObject parse(ConfigParseOptions options);
/** Possibly return a URL representing the resource; this may return null. */ /**
URL url(); * Returns a {@link ConfigOrigin} describing the origin of the parseable
* item.
*/
ConfigOrigin origin();
/** Get the initial options, which can be modified then passed to parse(). */ /**
* Get the initial options, which can be modified then passed to parse().
* These options will have the right description, includer, and other
* parameters already set up.
*/
ConfigParseOptions options(); ConfigParseOptions options();
} }

View file

@ -3,6 +3,28 @@
*/ */
package com.typesafe.config; package com.typesafe.config;
/**
* A set of options related to resolving substitutions. Substitutions use the
* <code>${foo.bar}</code> syntax and are documented in the <a
* href="https://github.com/havocp/config/blob/master/HOCON.md">HOCON</a> spec.
*
* <p>
* This object is immutable, so the "setters" return a new object.
*
* <p>
* Here is an example of creating a custom {@code ConfigResolveOptions}:
*
* <pre>
* ConfigResolveOptions options = ConfigResolveOptions.defaults()
* .setUseSystemProperties(false)
* .setUseSystemEnvironment(false)
* </pre>
*
* <p>
* In addition to {@link ConfigResolveOptions#defaults}, there's a prebuilt
* {@link ConfigResolveOptions#noSystem} which avoids looking at any system
* properties or environment variables.
*/
public final class ConfigResolveOptions { public final class ConfigResolveOptions {
private final boolean useSystemProperties; private final boolean useSystemProperties;
private final boolean useSystemEnvironment; private final boolean useSystemEnvironment;
@ -13,26 +35,68 @@ public final class ConfigResolveOptions {
this.useSystemEnvironment = useSystemEnvironment; this.useSystemEnvironment = useSystemEnvironment;
} }
/**
* Returns the default resolve options.
*
* @return the default resolve options
*/
public static ConfigResolveOptions defaults() { public static ConfigResolveOptions defaults() {
return new ConfigResolveOptions(true, true); return new ConfigResolveOptions(true, true);
} }
/**
* Returns resolve options that disable any reference to "system" data
* (system properties or environment variables).
*
* @return the resolve options with system properties and env variables
* disabled
*/
public static ConfigResolveOptions noSystem() { public static ConfigResolveOptions noSystem() {
return new ConfigResolveOptions(false, false); return defaults().setUseSystemEnvironment(false).setUseSystemProperties(false);
} }
/**
* Returns options with use of Java system properties set to the given
* value.
*
* @param value
* true to resolve substitutions falling back to Java system
* properties.
* @return options with requested setting for use of system properties
*/
public ConfigResolveOptions setUseSystemProperties(boolean value) { public ConfigResolveOptions setUseSystemProperties(boolean value) {
return new ConfigResolveOptions(value, useSystemEnvironment); return new ConfigResolveOptions(value, useSystemEnvironment);
} }
/**
* Returns options with use of environment variables set to the given value.
*
* @param value
* true to resolve substitutions falling back to environment
* variables.
* @return options with requested setting for use of environment variables
*/
public ConfigResolveOptions setUseSystemEnvironment(boolean value) { public ConfigResolveOptions setUseSystemEnvironment(boolean value) {
return new ConfigResolveOptions(useSystemProperties, value); return new ConfigResolveOptions(useSystemProperties, value);
} }
/**
* Returns whether the options enable use of system properties. This method
* is mostly used by the config lib internally, not by applications.
*
* @return true if system properties should be used
*/
public boolean getUseSystemProperties() { public boolean getUseSystemProperties() {
return useSystemProperties; return useSystemProperties;
} }
/**
* Returns whether the options enable use of system environment variables.
* This method is mostly used by the config lib internally, not by
* applications.
*
* @return true if environment variables should be used
*/
public boolean getUseSystemEnvironment() { public boolean getUseSystemEnvironment() {
return useSystemEnvironment; return useSystemEnvironment;
} }

View file

@ -1,33 +0,0 @@
/**
* Copyright (C) 2011 Typesafe Inc. <http://typesafe.com>
*/
package com.typesafe.config;
/**
* A root object. The only special thing about a root object is that you can
* resolve substitutions against it. So it can have a resolve() method that
* doesn't require you to pass in an object to resolve against.
*/
public interface ConfigRoot extends Config {
/**
* Returns a replacement root object with all substitutions (the
* "${foo.bar}" syntax) resolved. Substitutions are looked up in this root
* object. A configuration value tree must be resolved before you can use
* it. This method uses ConfigResolveOptions.defaults().
*
* @return an immutable object with substitutions resolved
*/
ConfigRoot resolve();
ConfigRoot resolve(ConfigResolveOptions options);
@Override
ConfigRoot withFallback(ConfigMergeable fallback);
/**
* Gets the global app name that this root represents.
*
* @return the app's root config path
*/
String rootPath();
}

View file

@ -3,6 +3,30 @@
*/ */
package com.typesafe.config; package com.typesafe.config;
/**
* The syntax of a character stream, <a href="http://json.org">JSON</a>, <a
* href="https://github.com/havocp/config/blob/master/HOCON.md">HOCON</a> aka
* ".conf", or <a href=
* "http://download.oracle.com/javase/7/docs/api/java/util/Properties.html#load%28java.io.Reader%29"
* >Java properties</a>.
*
*/
public enum ConfigSyntax { public enum ConfigSyntax {
JSON, CONF, PROPERTIES; /**
* Pedantically strict <a href="http://json.org">JSON</a> format; no
* comments, no unexpected commas, no duplicate keys in the same object.
*/
JSON,
/**
* The JSON-superset <a
* href="https://github.com/havocp/config/blob/master/HOCON.md">HOCON</a>
* format.
*/
CONF,
/**
* Standard <a href=
* "http://download.oracle.com/javase/7/docs/api/java/util/Properties.html#load%28java.io.Reader%29"
* >Java properties</a> format.
*/
PROPERTIES;
} }

View file

@ -4,32 +4,57 @@
package com.typesafe.config; package com.typesafe.config;
/** /**
* Interface implemented by any configuration value. From the perspective of * An immutable value, following the <a href="http://json.org">JSON</a> type
* users of this interface, the object is immutable. It is therefore safe to use * schema.
* from multiple threads. *
* <p>
* Because this object is immutable, it is safe to use from multiple threads and
* there's no need for "defensive copies."
*
* <p>
* <em>Do not implement {@code ConfigValue}</em>; it should only be implemented
* by the config library. Arbitrary implementations will not work because the
* library internals assume a specific concrete implementation. Also, this
* interface is likely to grow new methods over time, so third-party
* implementations will break.
*/ */
public interface ConfigValue extends ConfigMergeable { public interface ConfigValue extends ConfigMergeable {
/** /**
* The origin of the value, for debugging and error messages. * The origin of the value (file, line number, etc.), for debugging and
* error messages.
* *
* @return where the value came from * @return where the value came from
*/ */
ConfigOrigin origin(); ConfigOrigin origin();
/** /**
* The type of the value; matches the JSON type schema. * The {@link ConfigValueType} of the value; matches the JSON type schema.
* *
* @return value's type * @return value's type
*/ */
ConfigValueType valueType(); ConfigValueType valueType();
/** /**
* Returns the config value as a plain Java boxed value, should be a String, * Returns the value as a plain Java boxed value, that is, a {@code String},
* Number, etc. matching the valueType() of the ConfigValue. If the value is * {@code Number}, {@code Boolean}, {@code Map<String,Object>},
* a ConfigObject or ConfigList, it is recursively unwrapped. * {@code List<Object>}, or {@code null}, matching the {@link #valueType()}
* of this {@code ConfigValue}. If the value is a {@link ConfigObject} or
* {@link ConfigList}, it is recursively unwrapped.
*/ */
Object unwrapped(); Object unwrapped();
/**
* Renders the config value as a HOCON string. This method is primarily
* intended for debugging, so it tries to add helpful comments and
* whitespace. If the config value has not been resolved (see
* {@link Config#resolve}), it's possible that it can't be rendered as valid
* HOCON. In that case the rendering should still be useful for debugging
* but you might not be able to parse it.
*
* @return the rendered value
*/
String render();
@Override @Override
ConfigValue withFallback(ConfigMergeable other); ConfigValue withFallback(ConfigMergeable other);
} }

View file

@ -3,7 +3,6 @@
*/ */
package com.typesafe.config; package com.typesafe.config;
import java.util.Collection;
import java.util.Map; import java.util.Map;
import com.typesafe.config.impl.ConfigImpl; import com.typesafe.config.impl.ConfigImpl;
@ -14,6 +13,9 @@ import com.typesafe.config.impl.ConfigImpl;
* data structures. * data structures.
*/ */
public final class ConfigValueFactory { public final class ConfigValueFactory {
private ConfigValueFactory() {
}
/** /**
* Creates a ConfigValue from a plain Java boxed value, which may be a * Creates a ConfigValue from a plain Java boxed value, which may be a
* Boolean, Number, String, Map, Iterable, or null. A Map must be a Map from * Boolean, Number, String, Map, Iterable, or null. A Map must be a Map from
@ -23,23 +25,27 @@ public final class ConfigValueFactory {
* the Iterable is not an ordered collection, results could be strange, * the Iterable is not an ordered collection, results could be strange,
* since ConfigList is ordered. * since ConfigList is ordered.
* *
* <p>
* In a Map passed to fromAnyRef(), the map's keys are plain keys, not path * In a Map passed to fromAnyRef(), the map's keys are plain keys, not path
* expressions. So if your Map has a key "foo.bar" then you will get one * expressions. So if your Map has a key "foo.bar" then you will get one
* object with a key called "foo.bar", rather than an object with a key * object with a key called "foo.bar", rather than an object with a key
* "foo" containing another object with a key "bar". * "foo" containing another object with a key "bar".
* *
* <p>
* The originDescription will be used to set the origin() field on the * The originDescription will be used to set the origin() field on the
* ConfigValue. It should normally be the name of the file the values came * ConfigValue. It should normally be the name of the file the values came
* from, or something short describing the value such as "default settings". * from, or something short describing the value such as "default settings".
* The originDescription is prefixed to error messages so users can tell * The originDescription is prefixed to error messages so users can tell
* where problematic values are coming from. * where problematic values are coming from.
* *
* <p>
* Supplying the result of ConfigValue.unwrapped() to this function is * Supplying the result of ConfigValue.unwrapped() to this function is
* guaranteed to work and should give you back a ConfigValue that matches * guaranteed to work and should give you back a ConfigValue that matches
* the one you unwrapped. The re-wrapped ConfigValue will lose some * the one you unwrapped. The re-wrapped ConfigValue will lose some
* information that was present in the original such as its origin, but it * information that was present in the original such as its origin, but it
* will have matching values. * will have matching values.
* *
* <p>
* This function throws if you supply a value that cannot be converted to a * This function throws if you supply a value that cannot be converted to a
* ConfigValue, but supplying such a value is a bug in your program, so you * ConfigValue, but supplying such a value is a bug in your program, so you
* should never handle the exception. Just fix your program (or report a bug * should never handle the exception. Just fix your program (or report a bug
@ -57,23 +63,25 @@ public final class ConfigValueFactory {
/** /**
* See the fromAnyRef() documentation for details. This is a typesafe * See the fromAnyRef() documentation for details. This is a typesafe
* wrapper that only works on Map and returns ConfigObject rather than * wrapper that only works on {@link java.util.Map} and returns
* ConfigValue. * {@link ConfigObject} rather than {@link ConfigValue}.
* *
* <p>
* If your Map has a key "foo.bar" then you will get one object with a key * If your Map has a key "foo.bar" then you will get one object with a key
* called "foo.bar", rather than an object with a key "foo" containing * called "foo.bar", rather than an object with a key "foo" containing
* another object with a key "bar". The keys in the map are keys; not path * another object with a key "bar". The keys in the map are keys; not path
* expressions. That is, the Map corresponds exactly to a single * expressions. That is, the Map corresponds exactly to a single
* ConfigObject. The keys will not be parsed or modified, and the values are * {@code ConfigObject}. The keys will not be parsed or modified, and the
* wrapped in ConfigValue. To get nested ConfigObject, some of the values in * values are wrapped in ConfigValue. To get nested {@code ConfigObject},
* the map would have to be more maps. * some of the values in the map would have to be more maps.
* *
* There is a separate fromPathMap() that interprets the keys in the map as * <p>
* path expressions. * See also {@link ConfigFactory#parseMap(Map,String)} which interprets the
* keys in the map as path expressions.
* *
* @param values * @param values
* @param originDescription * @param originDescription
* @return * @return a new {@link ConfigObject} value
*/ */
public static ConfigObject fromMap(Map<String, ? extends Object> values, public static ConfigObject fromMap(Map<String, ? extends Object> values,
String originDescription) { String originDescription) {
@ -82,12 +90,12 @@ public final class ConfigValueFactory {
/** /**
* See the fromAnyRef() documentation for details. This is a typesafe * See the fromAnyRef() documentation for details. This is a typesafe
* wrapper that only works on Iterable and returns ConfigList rather than * wrapper that only works on {@link java.util.Iterable} and returns
* ConfigValue. * {@link ConfigList} rather than {@link ConfigValue}.
* *
* @param values * @param values
* @param originDescription * @param originDescription
* @return * @return a new {@link ConfigList} value
*/ */
public static ConfigList fromIterable(Iterable<? extends Object> values, public static ConfigList fromIterable(Iterable<? extends Object> values,
String originDescription) { String originDescription) {
@ -95,35 +103,39 @@ public final class ConfigValueFactory {
} }
/** /**
* See the other overload of fromAnyRef() for details, this one just uses a * See the other overload {@link #fromAnyRef(Object,String)} for details,
* default origin description. * this one just uses a default origin description.
* *
* @param object * @param object
* @return * @return a new {@link ConfigValue}
*/ */
public static ConfigValue fromAnyRef(Object object) { public static ConfigValue fromAnyRef(Object object) {
return fromAnyRef(object, null); return fromAnyRef(object, null);
} }
/** /**
* See the other overload of fromMap() for details, this one just uses a * See the other overload {@link #fromMap(Map,String)} for details, this one
* default origin description. * just uses a default origin description.
*
* <p>
* See also {@link ConfigFactory#parseMap(Map)} which interprets the keys in
* the map as path expressions.
* *
* @param values * @param values
* @return * @return a new {@link ConfigObject}
*/ */
public static ConfigObject fromMap(Map<String, ? extends Object> values) { public static ConfigObject fromMap(Map<String, ? extends Object> values) {
return fromMap(values, null); return fromMap(values, null);
} }
/** /**
* See the other overload of fromIterable() for details, this one just uses * See the other overload of {@link #fromIterable(Iterable, String)} for
* a default origin description. * details, this one just uses a default origin description.
* *
* @param values * @param values
* @return * @return a new {@link ConfigList}
*/ */
public static ConfigList fromIterable(Collection<? extends Object> values) { public static ConfigList fromIterable(Iterable<? extends Object> values) {
return fromIterable(values, null); return fromIterable(values, null);
} }
} }

View file

@ -4,7 +4,8 @@
package com.typesafe.config; package com.typesafe.config;
/** /**
* The type of a configuration value. Value types follow the JSON type schema. * The type of a configuration value (following the <a
* href="http://json.org">JSON</a> type schema).
*/ */
public enum ConfigValueType { public enum ConfigValueType {
OBJECT, LIST, NUMBER, BOOLEAN, NULL, STRING OBJECT, LIST, NUMBER, BOOLEAN, NULL, STRING

View file

@ -61,19 +61,23 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
* (just returns null if path not found). Does however resolve the path, if * (just returns null if path not found). Does however resolve the path, if
* resolver != null. * resolver != null.
*/ */
protected ConfigValue peekPath(Path path, SubstitutionResolver resolver, protected AbstractConfigValue peekPath(Path path, SubstitutionResolver resolver,
int depth, ConfigResolveOptions options) { int depth, ConfigResolveOptions options) {
return peekPath(this, path, resolver, depth, options); return peekPath(this, path, resolver, depth, options);
} }
private static ConfigValue peekPath(AbstractConfigObject self, Path path, AbstractConfigValue peekPath(Path path) {
return peekPath(this, path, null, 0, null);
}
private static AbstractConfigValue peekPath(AbstractConfigObject self, Path path,
SubstitutionResolver resolver, int depth, SubstitutionResolver resolver, int depth,
ConfigResolveOptions options) { ConfigResolveOptions options) {
String key = path.first(); String key = path.first();
Path next = path.remainder(); Path next = path.remainder();
if (next == null) { if (next == null) {
ConfigValue v = self.peek(key, resolver, depth, options); AbstractConfigValue v = self.peek(key, resolver, depth, options);
return v; return v;
} else { } else {
// it's important to ONLY resolve substitutions here, not // it's important to ONLY resolve substitutions here, not
@ -164,18 +168,13 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
if (stack.isEmpty()) if (stack.isEmpty())
throw new ConfigException.BugOrBroken( throw new ConfigException.BugOrBroken(
"can't merge origins on empty list"); "can't merge origins on empty list");
final String prefix = "merge of "; List<ConfigOrigin> origins = new ArrayList<ConfigOrigin>();
StringBuilder sb = new StringBuilder();
ConfigOrigin firstOrigin = null; ConfigOrigin firstOrigin = null;
int numMerged = 0; int numMerged = 0;
for (AbstractConfigValue v : stack) { for (AbstractConfigValue v : stack) {
if (firstOrigin == null) if (firstOrigin == null)
firstOrigin = v.origin(); firstOrigin = v.origin();
String desc = v.origin().description();
if (desc.startsWith(prefix))
desc = desc.substring(prefix.length());
if (v instanceof AbstractConfigObject if (v instanceof AbstractConfigObject
&& ((AbstractConfigObject) v).resolveStatus() == ResolveStatus.RESOLVED && ((AbstractConfigObject) v).resolveStatus() == ResolveStatus.RESOLVED
&& ((ConfigObject) v).isEmpty()) { && ((ConfigObject) v).isEmpty()) {
@ -183,22 +182,17 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
// config in the description, since they are // config in the description, since they are
// likely to be "implementation details" // likely to be "implementation details"
} else { } else {
sb.append(desc); origins.add(v.origin());
sb.append(",");
numMerged += 1; numMerged += 1;
} }
} }
if (numMerged > 0) {
sb.setLength(sb.length() - 1); // chop comma if (numMerged == 0) {
if (numMerged > 1) { // the configs were all empty, so just use the first one
return new SimpleConfigOrigin(prefix + sb.toString()); origins.add(firstOrigin);
} else {
return new SimpleConfigOrigin(sb.toString());
}
} else {
// the configs were all empty.
return firstOrigin;
} }
return SimpleConfigOrigin.mergeOrigins(origins);
} }
static ConfigOrigin mergeOrigins(AbstractConfigObject... stack) { static ConfigOrigin mergeOrigins(AbstractConfigObject... stack) {
@ -210,6 +204,8 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
Map<String, AbstractConfigValue> changes = null; Map<String, AbstractConfigValue> changes = null;
for (String k : keySet()) { for (String k : keySet()) {
AbstractConfigValue v = peek(k); AbstractConfigValue v = peek(k);
// "modified" may be null, which means remove the child;
// to do that we put null in the "changes" map.
AbstractConfigValue modified = modifier.modifyChild(v); AbstractConfigValue modified = modifier.modifyChild(v);
if (modified != v) { if (modified != v) {
if (changes == null) if (changes == null)
@ -223,7 +219,12 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
Map<String, AbstractConfigValue> modified = new HashMap<String, AbstractConfigValue>(); Map<String, AbstractConfigValue> modified = new HashMap<String, AbstractConfigValue>();
for (String k : keySet()) { for (String k : keySet()) {
if (changes.containsKey(k)) { if (changes.containsKey(k)) {
modified.put(k, changes.get(k)); AbstractConfigValue newValue = changes.get(k);
if (newValue != null) {
modified.put(k, newValue);
} else {
// remove this child; don't put it in the new map.
}
} else { } else {
modified.put(k, peek(k)); modified.put(k, peek(k));
} }
@ -271,20 +272,36 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
} }
@Override @Override
public String toString() { protected void render(StringBuilder sb, int indent, boolean formatted) {
StringBuilder sb = new StringBuilder(); if (isEmpty()) {
sb.append(valueType().name()); sb.append("{}");
sb.append("("); } else {
for (String k : keySet()) { sb.append("{");
sb.append(k); if (formatted)
sb.append("->"); sb.append('\n');
sb.append(peek(k).toString()); for (String k : keySet()) {
sb.append(","); AbstractConfigValue v = peek(k);
if (formatted) {
indent(sb, indent + 1);
sb.append("# ");
sb.append(v.origin().description());
sb.append("\n");
indent(sb, indent + 1);
}
v.render(sb, indent + 1, k, formatted);
sb.append(",");
if (formatted)
sb.append('\n');
}
// chop comma or newline
sb.setLength(sb.length() - 1);
if (formatted) {
sb.setLength(sb.length() - 1); // also chop comma
sb.append("\n"); // put a newline back
indent(sb, indent);
}
sb.append("}");
} }
if (!keySet().isEmpty())
sb.setLength(sb.length() - 1); // chop comma
sb.append(")");
return sb.toString();
} }
private static boolean mapEquals(Map<String, ConfigValue> a, private static boolean mapEquals(Map<String, ConfigValue> a,

View file

@ -162,8 +162,39 @@ abstract class AbstractConfigValue implements ConfigValue {
} }
@Override @Override
public String toString() { public final String toString() {
return valueType().name() + "(" + unwrapped() + ")"; StringBuilder sb = new StringBuilder();
render(sb, 0, null /* atKey */, false /* formatted */);
return getClass().getSimpleName() + "(" + sb.toString() + ")";
}
protected static void indent(StringBuilder sb, int indent) {
int remaining = indent;
while (remaining > 0) {
sb.append(" ");
--remaining;
}
}
protected void render(StringBuilder sb, int indent, String atKey, boolean formatted) {
if (atKey != null) {
sb.append(ConfigUtil.renderJsonString(atKey));
sb.append(" : ");
}
render(sb, indent, formatted);
}
protected void render(StringBuilder sb, int indent, boolean formatted) {
Object u = unwrapped();
sb.append(u.toString());
}
@Override
public final String render() {
StringBuilder sb = new StringBuilder();
render(sb, 0, null, true /* formatted */);
return sb.toString();
} }
// toString() is a debugging-oriented string but this is defined // toString() is a debugging-oriented string but this is defined

View file

@ -5,6 +5,7 @@ package com.typesafe.config.impl;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigException;
@ -76,10 +77,12 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements
AbstractConfigValue merged = null; AbstractConfigValue merged = null;
for (AbstractConfigValue v : stack) { for (AbstractConfigValue v : stack) {
AbstractConfigValue resolved = resolver.resolve(v, depth, options); AbstractConfigValue resolved = resolver.resolve(v, depth, options);
if (merged == null) if (resolved != null) {
merged = resolved; if (merged == null)
else merged = resolved;
merged = merged.withFallback(resolved); else
merged = merged.withFallback(resolved);
}
} }
return merged; return merged;
@ -160,16 +163,58 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements
} }
@Override @Override
public String toString() { protected void render(StringBuilder sb, int indent, String atKey, boolean formatted) {
StringBuilder sb = new StringBuilder(); render(stack, sb, indent, atKey, formatted);
sb.append("DELAYED_MERGE"); }
sb.append("(");
for (Object s : stack) { // static method also used by ConfigDelayedMergeObject.
sb.append(s.toString()); static void render(List<AbstractConfigValue> stack, StringBuilder sb, int indent, String atKey,
sb.append(","); boolean formatted) {
if (formatted) {
sb.append("# unresolved merge of " + stack.size() + " values follows (\n");
if (atKey == null) {
indent(sb, indent);
sb.append("# this unresolved merge will not be parseable because it's at the root of the object\n");
sb.append("# the HOCON format has no way to list multiple root objects in a single file\n");
}
}
List<AbstractConfigValue> reversed = new ArrayList<AbstractConfigValue>();
reversed.addAll(stack);
Collections.reverse(reversed);
int i = 0;
for (AbstractConfigValue v : reversed) {
if (formatted) {
indent(sb, indent);
if (atKey != null) {
sb.append("# unmerged value " + i + " for key "
+ ConfigUtil.renderJsonString(atKey) + " from ");
} else {
sb.append("# unmerged value " + i + " from ");
}
i += 1;
sb.append(v.origin().description());
sb.append("\n");
indent(sb, indent);
}
if (atKey != null) {
sb.append(ConfigUtil.renderJsonString(atKey));
sb.append(" : ");
}
v.render(sb, indent, formatted);
sb.append(",");
if (formatted)
sb.append('\n');
}
// chop comma or newline
sb.setLength(sb.length() - 1);
if (formatted) {
sb.setLength(sb.length() - 1); // also chop comma
sb.append("\n"); // put a newline back
indent(sb, indent);
sb.append("# ) end of unresolved merge\n");
} }
sb.setLength(sb.length() - 1); // chop comma
sb.append(")");
return sb.toString();
} }
} }

View file

@ -139,17 +139,8 @@ class ConfigDelayedMergeObject extends AbstractConfigObject implements
} }
@Override @Override
public String toString() { protected void render(StringBuilder sb, int indent, String atKey, boolean formatted) {
StringBuilder sb = new StringBuilder(); ConfigDelayedMerge.render(stack, sb, indent, atKey, formatted);
sb.append("DELAYED_MERGE_OBJECT");
sb.append("(");
for (Object s : stack) {
sb.append(s.toString());
sb.append(",");
}
sb.setLength(sb.length() - 1); // chop comma
sb.append(")");
return sb.toString();
} }
private static ConfigException notResolved() { private static ConfigException notResolved() {

View file

@ -19,7 +19,6 @@ import com.typesafe.config.ConfigObject;
import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigParseOptions; import com.typesafe.config.ConfigParseOptions;
import com.typesafe.config.ConfigParseable; import com.typesafe.config.ConfigParseable;
import com.typesafe.config.ConfigRoot;
import com.typesafe.config.ConfigSyntax; import com.typesafe.config.ConfigSyntax;
import com.typesafe.config.ConfigValue; import com.typesafe.config.ConfigValue;
@ -45,8 +44,7 @@ public class ConfigImpl {
obj = p.parse(p.options().setAllowMissing( obj = p.parse(p.options().setAllowMissing(
options.getAllowMissing())); options.getAllowMissing()));
} else { } else {
obj = SimpleConfigObject.emptyMissing(new SimpleConfigOrigin( obj = SimpleConfigObject.emptyMissing(SimpleConfigOrigin.newSimple(name));
name));
} }
} else { } else {
ConfigParseable confHandle = source.nameToParseable(name + ".conf"); ConfigParseable confHandle = source.nameToParseable(name + ".conf");
@ -56,13 +54,13 @@ public class ConfigImpl {
if (!options.getAllowMissing() && confHandle == null if (!options.getAllowMissing() && confHandle == null
&& jsonHandle == null && propsHandle == null) { && jsonHandle == null && propsHandle == null) {
throw new ConfigException.IO(new SimpleConfigOrigin(name), throw new ConfigException.IO(SimpleConfigOrigin.newSimple(name),
"No config files {.conf,.json,.properties} found"); "No config files {.conf,.json,.properties} found");
} }
ConfigSyntax syntax = options.getSyntax(); ConfigSyntax syntax = options.getSyntax();
obj = SimpleConfigObject.empty(new SimpleConfigOrigin(name)); obj = SimpleConfigObject.empty(SimpleConfigOrigin.newSimple(name));
if (confHandle != null if (confHandle != null
&& (syntax == null || syntax == ConfigSyntax.CONF)) { && (syntax == null || syntax == ConfigSyntax.CONF)) {
obj = confHandle.parse(confHandle.options() obj = confHandle.parse(confHandle.options()
@ -89,39 +87,13 @@ public class ConfigImpl {
return obj; return obj;
} }
private static String makeResourceBasename(Path path) {
StringBuilder sb = new StringBuilder("/");
String next = path.first();
Path remaining = path.remainder();
while (next != null) {
sb.append(next);
sb.append('-');
if (remaining == null)
break;
next = remaining.first();
remaining = remaining.remainder();
}
sb.setLength(sb.length() - 1); // chop extra hyphen
return sb.toString();
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */ /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static ConfigObject parseResourcesForPath(String expression, public static ConfigObject parseResourcesAnySyntax(final Class<?> klass,
final ConfigParseOptions baseOptions) {
Path path = Parser.parsePath(expression);
String basename = makeResourceBasename(path);
return parseResourceAnySyntax(ConfigImpl.class, basename, baseOptions);
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static ConfigObject parseResourceAnySyntax(final Class<?> klass,
String resourceBasename, final ConfigParseOptions baseOptions) { String resourceBasename, final ConfigParseOptions baseOptions) {
NameSource source = new NameSource() { NameSource source = new NameSource() {
@Override @Override
public ConfigParseable nameToParseable(String name) { public ConfigParseable nameToParseable(String name) {
return Parseable.newResource(klass, name, baseOptions); return Parseable.newResources(klass, name, baseOptions);
} }
}; };
return fromBasename(source, resourceBasename, baseOptions); return fromBasename(source, resourceBasename, baseOptions);
@ -139,16 +111,9 @@ public class ConfigImpl {
return fromBasename(source, basename.getPath(), baseOptions); return fromBasename(source, basename.getPath(), baseOptions);
} }
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static ConfigRoot emptyRoot(String rootPath, String originDescription) {
String desc = originDescription != null ? originDescription : rootPath;
return emptyObject(desc).toConfig().asRoot(
Path.newPath(rootPath));
}
static AbstractConfigObject emptyObject(String originDescription) { static AbstractConfigObject emptyObject(String originDescription) {
ConfigOrigin origin = originDescription != null ? new SimpleConfigOrigin( ConfigOrigin origin = originDescription != null ? SimpleConfigOrigin
originDescription) : null; .newSimple(originDescription) : null;
return emptyObject(origin); return emptyObject(origin);
} }
@ -162,8 +127,8 @@ public class ConfigImpl {
} }
// default origin for values created with fromAnyRef and no origin specified // default origin for values created with fromAnyRef and no origin specified
final private static ConfigOrigin defaultValueOrigin = new SimpleConfigOrigin( final private static ConfigOrigin defaultValueOrigin = SimpleConfigOrigin
"hardcoded value"); .newSimple("hardcoded value");
final private static ConfigBoolean defaultTrueValue = new ConfigBoolean( final private static ConfigBoolean defaultTrueValue = new ConfigBoolean(
defaultValueOrigin, true); defaultValueOrigin, true);
final private static ConfigBoolean defaultFalseValue = new ConfigBoolean( final private static ConfigBoolean defaultFalseValue = new ConfigBoolean(
@ -196,7 +161,7 @@ public class ConfigImpl {
if (originDescription == null) if (originDescription == null)
return defaultValueOrigin; return defaultValueOrigin;
else else
return new SimpleConfigOrigin(originDescription); return SimpleConfigOrigin.newSimple(originDescription);
} }
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */ /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
@ -290,17 +255,6 @@ public class ConfigImpl {
} }
} }
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static ConfigRoot systemPropertiesRoot(String rootPath) {
Path path = Parser.parsePath(rootPath);
try {
return systemPropertiesAsConfigObject().toConfig().getConfig(rootPath)
.asRoot(path);
} catch (ConfigException.Missing e) {
return emptyObject("system properties").toConfig().asRoot(path);
}
}
private static class SimpleIncluder implements ConfigIncluder { private static class SimpleIncluder implements ConfigIncluder {
private ConfigIncluder fallback; private ConfigIncluder fallback;
@ -346,29 +300,26 @@ public class ConfigImpl {
} }
} }
private static ConfigIncluder defaultIncluder = null; private static class DefaultIncluderHolder {
static final ConfigIncluder defaultIncluder = new SimpleIncluder(null);
synchronized static ConfigIncluder defaultIncluder() {
if (defaultIncluder == null) {
defaultIncluder = new SimpleIncluder(null);
}
return defaultIncluder;
} }
private static AbstractConfigObject systemProperties = null; static ConfigIncluder defaultIncluder() {
return DefaultIncluderHolder.defaultIncluder;
synchronized static AbstractConfigObject systemPropertiesAsConfigObject() {
if (systemProperties == null) {
systemProperties = loadSystemProperties();
}
return systemProperties;
} }
private static AbstractConfigObject loadSystemProperties() { private static AbstractConfigObject loadSystemProperties() {
return (AbstractConfigObject) Parseable.newProperties( return (AbstractConfigObject) Parseable.newProperties(System.getProperties(),
System.getProperties(), ConfigParseOptions.defaults().setOriginDescription("system properties")).parse();
ConfigParseOptions.defaults().setOriginDescription( }
"system properties")).parse();
private static class SystemPropertiesHolder {
// this isn't final due to the reloadSystemPropertiesConfig() hack below
static AbstractConfigObject systemProperties = loadSystemProperties();
}
static AbstractConfigObject systemPropertiesAsConfigObject() {
return SystemPropertiesHolder.systemProperties;
} }
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */ /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
@ -376,18 +327,10 @@ public class ConfigImpl {
return systemPropertiesAsConfigObject().toConfig(); return systemPropertiesAsConfigObject().toConfig();
} }
// this is a hack to let us set system props in the test suite // this is a hack to let us set system props in the test suite.
synchronized static void dropSystemPropertiesConfig() { // obviously not thread-safe.
systemProperties = null; static void reloadSystemPropertiesConfig() {
} SystemPropertiesHolder.systemProperties = loadSystemProperties();
private static AbstractConfigObject envVariables = null;
synchronized static AbstractConfigObject envVariablesAsConfigObject() {
if (envVariables == null) {
envVariables = loadEnvVariables();
}
return envVariables;
} }
private static AbstractConfigObject loadEnvVariables() { private static AbstractConfigObject loadEnvVariables() {
@ -395,15 +338,37 @@ public class ConfigImpl {
Map<String, AbstractConfigValue> m = new HashMap<String, AbstractConfigValue>(); Map<String, AbstractConfigValue> m = new HashMap<String, AbstractConfigValue>();
for (Map.Entry<String, String> entry : env.entrySet()) { for (Map.Entry<String, String> entry : env.entrySet()) {
String key = entry.getKey(); String key = entry.getKey();
m.put(key, new ConfigString( m.put(key,
new SimpleConfigOrigin("env var " + key), entry.getValue())); new ConfigString(SimpleConfigOrigin.newSimple("env var " + key), entry
.getValue()));
} }
return new SimpleConfigObject(new SimpleConfigOrigin("env variables"), return new SimpleConfigObject(SimpleConfigOrigin.newSimple("env variables"),
m, ResolveStatus.RESOLVED, false /* ignoresFallbacks */); m, ResolveStatus.RESOLVED, false /* ignoresFallbacks */);
} }
private static class EnvVariablesHolder {
static final AbstractConfigObject envVariables = loadEnvVariables();
}
static AbstractConfigObject envVariablesAsConfigObject() {
return EnvVariablesHolder.envVariables;
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */ /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static Config envVariablesAsConfig() { public static Config envVariablesAsConfig() {
return envVariablesAsConfigObject().toConfig(); return envVariablesAsConfigObject().toConfig();
} }
private static class ReferenceHolder {
private static final Config unresolvedResources = Parseable
.newResources(ConfigImpl.class, "/reference.conf", ConfigParseOptions.defaults())
.parse().toConfig();
static final Config referenceConfig = systemPropertiesAsConfig().withFallback(
unresolvedResources).resolve();
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static Config defaultReference() {
return ReferenceHolder.referenceConfig;
}
} }

View file

@ -34,4 +34,9 @@ final class ConfigNull extends AbstractConfigValue {
String transformToString() { String transformToString() {
return "null"; return "null";
} }
@Override
protected void render(StringBuilder sb, int indent, boolean formatted) {
sb.append("null");
}
} }

View file

@ -29,4 +29,9 @@ final class ConfigString extends AbstractConfigValue {
String transformToString() { String transformToString() {
return value; return value;
} }
@Override
protected void render(StringBuilder sb, int indent, boolean formatted) {
sb.append(ConfigUtil.renderJsonString(value));
}
} }

View file

@ -22,8 +22,8 @@ import com.typesafe.config.ConfigValueType;
final class ConfigSubstitution extends AbstractConfigValue implements final class ConfigSubstitution extends AbstractConfigValue implements
Unmergeable { Unmergeable {
// this is a list of String and Path where the Path // this is a list of String and SubstitutionExpression where the
// have to be resolved to values, then if there's more // SubstitutionExpression has to be resolved to values, then if there's more
// than one piece everything is stringified and concatenated // than one piece everything is stringified and concatenated
final private List<Object> pieces; final private List<Object> pieces;
// the length of any prefixes added with relativized() // the length of any prefixes added with relativized()
@ -40,19 +40,24 @@ final class ConfigSubstitution extends AbstractConfigValue implements
this.pieces = pieces; this.pieces = pieces;
this.prefixLength = prefixLength; this.prefixLength = prefixLength;
this.ignoresFallbacks = ignoresFallbacks; this.ignoresFallbacks = ignoresFallbacks;
for (Object p : pieces) {
if (p instanceof Path)
throw new RuntimeException("broken here");
}
} }
@Override @Override
public ConfigValueType valueType() { public ConfigValueType valueType() {
throw new ConfigException.NotResolved( throw new ConfigException.NotResolved(
"tried to get value type on an unresolved substitution: " "need to call resolve() on root config; tried to get value type on an unresolved substitution: "
+ this); + this);
} }
@Override @Override
public Object unwrapped() { public Object unwrapped() {
throw new ConfigException.NotResolved( throw new ConfigException.NotResolved(
"tried to unwrap an unresolved substitution: " + this); "need to call resolve() on root config; tried to unwrap an unresolved substitution: "
+ this);
} }
@Override @Override
@ -124,15 +129,15 @@ final class ConfigSubstitution extends AbstractConfigValue implements
return result; return result;
} }
private ConfigValue resolve(SubstitutionResolver resolver, Path subst, private ConfigValue resolve(SubstitutionResolver resolver, SubstitutionExpression subst,
int depth, ConfigResolveOptions options) { int depth, ConfigResolveOptions options) {
ConfigValue result = findInObject(resolver.root(), resolver, subst, ConfigValue result = findInObject(resolver.root(), resolver, subst.path(),
depth, options); depth, options);
// when looking up system props and env variables, // when looking up system props and env variables,
// we don't want the prefix that was added when // we don't want the prefix that was added when
// we were included in another file. // we were included in another file.
Path unprefixed = subst.subPath(prefixLength); Path unprefixed = subst.path().subPath(prefixLength);
if (result == null && options.getUseSystemProperties()) { if (result == null && options.getUseSystemProperties()) {
result = findInObject(ConfigImpl.systemPropertiesAsConfigObject(), null, result = findInObject(ConfigImpl.systemPropertiesAsConfigObject(), null,
@ -144,10 +149,6 @@ final class ConfigSubstitution extends AbstractConfigValue implements
unprefixed, depth, options); unprefixed, depth, options);
} }
if (result == null) {
result = new ConfigNull(origin());
}
return result; return result;
} }
@ -160,28 +161,40 @@ final class ConfigSubstitution extends AbstractConfigValue implements
if (p instanceof String) { if (p instanceof String) {
sb.append((String) p); sb.append((String) p);
} else { } else {
ConfigValue v = resolve(resolver, (Path) p, depth, options); SubstitutionExpression exp = (SubstitutionExpression) p;
switch (v.valueType()) { ConfigValue v = resolve(resolver, exp, depth, options);
case NULL:
// nothing; becomes empty string if (v == null) {
break; if (exp.optional()) {
case LIST: // append nothing to StringBuilder
case OBJECT: } else {
// cannot substitute lists and objects into strings throw new ConfigException.UnresolvedSubstitution(origin(),
throw new ConfigException.WrongType(v.origin(), exp.toString());
((Path) p).render(), }
"not a list or object", v.valueType().name()); } else {
default: switch (v.valueType()) {
sb.append(((AbstractConfigValue) v).transformToString()); case LIST:
case OBJECT:
// cannot substitute lists and objects into strings
throw new ConfigException.WrongType(v.origin(), exp.path().render(),
"not a list or object", v.valueType().name());
default:
sb.append(((AbstractConfigValue) v).transformToString());
}
} }
} }
} }
return new ConfigString(origin(), sb.toString()); return new ConfigString(origin(), sb.toString());
} else { } else {
if (!(pieces.get(0) instanceof Path)) if (!(pieces.get(0) instanceof SubstitutionExpression))
throw new ConfigException.BugOrBroken( throw new ConfigException.BugOrBroken(
"ConfigSubstitution should never contain a single String piece"); "ConfigSubstitution should never contain a single String piece");
return resolve(resolver, (Path) pieces.get(0), depth, options); SubstitutionExpression exp = (SubstitutionExpression) pieces.get(0);
ConfigValue v = resolve(resolver, exp, depth, options);
if (v == null && !exp.optional()) {
throw new ConfigException.UnresolvedSubstitution(origin(), exp.toString());
}
return v;
} }
} }
@ -210,8 +223,10 @@ final class ConfigSubstitution extends AbstractConfigValue implements
ConfigSubstitution relativized(Path prefix) { ConfigSubstitution relativized(Path prefix) {
List<Object> newPieces = new ArrayList<Object>(); List<Object> newPieces = new ArrayList<Object>();
for (Object p : pieces) { for (Object p : pieces) {
if (p instanceof Path) { if (p instanceof SubstitutionExpression) {
newPieces.add(((Path) p).prepend(prefix)); SubstitutionExpression exp = (SubstitutionExpression) p;
newPieces.add(exp.changePath(exp.path().prepend(prefix)));
} else { } else {
newPieces.add(p); newPieces.add(p);
} }
@ -243,16 +258,13 @@ final class ConfigSubstitution extends AbstractConfigValue implements
} }
@Override @Override
public String toString() { protected void render(StringBuilder sb, int indent, boolean formatted) {
StringBuilder sb = new StringBuilder();
sb.append("SUBST");
sb.append("(");
for (Object p : pieces) { for (Object p : pieces) {
sb.append(p.toString()); if (p instanceof SubstitutionExpression) {
sb.append(","); sb.append(p.toString());
} else {
sb.append(ConfigUtil.renderJsonString((String) p));
}
} }
sb.setLength(sb.length() - 1); // chop comma
sb.append(")");
return sb.toString();
} }
} }

View file

@ -0,0 +1,8 @@
package com.typesafe.config.impl;
enum OriginType {
GENERIC,
FILE,
URL,
RESOURCE
}

View file

@ -17,6 +17,7 @@ import java.net.MalformedURLException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.util.Enumeration;
import java.util.Iterator; import java.util.Iterator;
import java.util.Properties; import java.util.Properties;
@ -39,6 +40,7 @@ import com.typesafe.config.ConfigValue;
public abstract class Parseable implements ConfigParseable { public abstract class Parseable implements ConfigParseable {
private ConfigIncludeContext includeContext; private ConfigIncludeContext includeContext;
private ConfigParseOptions initialOptions; private ConfigParseOptions initialOptions;
private ConfigOrigin initialOrigin;
protected Parseable() { protected Parseable() {
@ -54,9 +56,6 @@ public abstract class Parseable implements ConfigParseable {
} }
ConfigParseOptions modified = baseOptions.setSyntax(syntax); ConfigParseOptions modified = baseOptions.setSyntax(syntax);
if (modified.getOriginDescription() == null)
modified = modified.setOriginDescription(originDescription());
modified = modified.appendIncluder(ConfigImpl.defaultIncluder()); modified = modified.appendIncluder(ConfigImpl.defaultIncluder());
return modified; return modified;
@ -71,6 +70,11 @@ public abstract class Parseable implements ConfigParseable {
return Parseable.this.relativeTo(filename); return Parseable.this.relativeTo(filename);
} }
}; };
if (initialOptions.getOriginDescription() != null)
initialOrigin = SimpleConfigOrigin.newSimple(initialOptions.getOriginDescription());
else
initialOrigin = createOrigin();
} }
// the general idea is that any work should be in here, not in the // the general idea is that any work should be in here, not in the
@ -108,33 +112,26 @@ public abstract class Parseable implements ConfigParseable {
return forceParsedToObject(parseValue(baseOptions)); return forceParsedToObject(parseValue(baseOptions));
} }
AbstractConfigValue parseValue(ConfigParseOptions baseOptions) { final AbstractConfigValue parseValue(ConfigParseOptions baseOptions) {
// note that we are NOT using our "options" and "origin" fields, // note that we are NOT using our "initialOptions",
// but using the ones from the passed-in options. The idea is that // but using the ones from the passed-in options. The idea is that
// callers can get our original options and then parse with different // callers can get our original options and then parse with different
// ones if they want. // ones if they want.
ConfigParseOptions options = fixupOptions(baseOptions); ConfigParseOptions options = fixupOptions(baseOptions);
ConfigOrigin origin = new SimpleConfigOrigin(
options.getOriginDescription()); // passed-in options can override origin
ConfigOrigin origin;
if (options.getOriginDescription() != null)
origin = SimpleConfigOrigin.newSimple(options.getOriginDescription());
else
origin = initialOrigin;
return parseValue(origin, options); return parseValue(origin, options);
} }
protected AbstractConfigValue parseValue(ConfigOrigin origin, final private AbstractConfigValue parseValue(ConfigOrigin origin,
ConfigParseOptions finalOptions) { ConfigParseOptions finalOptions) {
try { try {
Reader reader = reader(); return rawParseValue(origin, finalOptions);
try {
if (finalOptions.getSyntax() == ConfigSyntax.PROPERTIES) {
return PropertiesParser.parse(reader, origin);
} else {
Iterator<Token> tokens = Tokenizer.tokenize(origin, reader,
finalOptions.getSyntax());
return Parser.parse(tokens, origin, finalOptions,
includeContext());
}
} finally {
reader.close();
}
} catch (IOException e) { } catch (IOException e) {
if (finalOptions.getAllowMissing()) { if (finalOptions.getAllowMissing()) {
return SimpleConfigObject.emptyMissing(origin); return SimpleConfigObject.emptyMissing(origin);
@ -144,6 +141,28 @@ public abstract class Parseable implements ConfigParseable {
} }
} }
// this is parseValue without post-processing the IOException or handling
// options.getAllowMissing()
protected AbstractConfigValue rawParseValue(ConfigOrigin origin, ConfigParseOptions finalOptions)
throws IOException {
Reader reader = reader();
try {
return rawParseValue(reader, origin, finalOptions);
} finally {
reader.close();
}
}
protected AbstractConfigValue rawParseValue(Reader reader, ConfigOrigin origin,
ConfigParseOptions finalOptions) throws IOException {
if (finalOptions.getSyntax() == ConfigSyntax.PROPERTIES) {
return PropertiesParser.parse(reader, origin);
} else {
Iterator<Token> tokens = Tokenizer.tokenize(origin, reader, finalOptions.getSyntax());
return Parser.parse(tokens, origin, finalOptions, includeContext());
}
}
public ConfigObject parse() { public ConfigObject parse() {
return forceParsedToObject(parseValue(options())); return forceParsedToObject(parseValue(options()));
} }
@ -152,13 +171,13 @@ public abstract class Parseable implements ConfigParseable {
return parseValue(options()); return parseValue(options());
} }
abstract String originDescription();
@Override @Override
public URL url() { public final ConfigOrigin origin() {
return null; return initialOrigin;
} }
protected abstract ConfigOrigin createOrigin();
@Override @Override
public ConfigParseOptions options() { public ConfigParseOptions options() {
return initialOptions; return initialOptions;
@ -228,34 +247,6 @@ public abstract class Parseable implements ConfigParseable {
} }
} }
private final static class ParseableInputStream extends Parseable {
final private InputStream input;
ParseableInputStream(InputStream input, ConfigParseOptions options) {
this.input = input;
postConstruct(options);
}
@Override
protected Reader reader() {
return doNotClose(readerFromStream(input));
}
@Override
String originDescription() {
return "InputStream";
}
}
/**
* note that we will never close this stream; you have to do it when parsing
* is complete.
*/
public static Parseable newInputStream(InputStream input,
ConfigParseOptions options) {
return new ParseableInputStream(input, options);
}
private final static class ParseableReader extends Parseable { private final static class ParseableReader extends Parseable {
final private Reader reader; final private Reader reader;
@ -270,8 +261,8 @@ public abstract class Parseable implements ConfigParseable {
} }
@Override @Override
String originDescription() { protected ConfigOrigin createOrigin() {
return "Reader"; return SimpleConfigOrigin.newSimple("Reader");
} }
} }
@ -297,8 +288,8 @@ public abstract class Parseable implements ConfigParseable {
} }
@Override @Override
String originDescription() { protected ConfigOrigin createOrigin() {
return "String"; return SimpleConfigOrigin.newSimple("String");
} }
} }
@ -335,13 +326,8 @@ public abstract class Parseable implements ConfigParseable {
} }
@Override @Override
String originDescription() { protected ConfigOrigin createOrigin() {
return input.toExternalForm(); return SimpleConfigOrigin.newURL(input);
}
@Override
public URL url() {
return input;
} }
@Override @Override
@ -387,17 +373,8 @@ public abstract class Parseable implements ConfigParseable {
} }
@Override @Override
String originDescription() { protected ConfigOrigin createOrigin() {
return input.getPath(); return SimpleConfigOrigin.newFile(input.getPath());
}
@Override
public URL url() {
try {
return input.toURI().toURL();
} catch (MalformedURLException e) {
return null;
}
} }
@Override @Override
@ -410,25 +387,61 @@ public abstract class Parseable implements ConfigParseable {
return new ParseableFile(input, options); return new ParseableFile(input, options);
} }
private final static class ParseableResource extends Parseable { private final static class ParseableResources extends Parseable {
final private Class<?> klass; final private ClassLoader loader;
final private String resource; final private String resource;
ParseableResource(Class<?> klass, String resource, ParseableResources(ClassLoader loader, String resource,
ConfigParseOptions options) { ConfigParseOptions options) {
this.klass = klass; this.loader = loader;
this.resource = resource; this.resource = resource;
postConstruct(options); postConstruct(options);
} }
@Override @Override
protected Reader reader() throws IOException { protected Reader reader() throws IOException {
InputStream stream = klass.getResourceAsStream(resource); throw new ConfigException.BugOrBroken(
if (stream == null) { "reader() should not be called on resources");
throw new IOException("resource not found on classpath: " }
+ resource);
@Override
protected AbstractConfigObject rawParseValue(ConfigOrigin origin,
ConfigParseOptions finalOptions) throws IOException {
Enumeration<URL> e = loader.getResources(resource);
if (!e.hasMoreElements()) {
throw new IOException("resource not found on classpath: " + resource);
} }
return readerFromStream(stream); AbstractConfigObject merged = SimpleConfigObject.empty(origin);
while (e.hasMoreElements()) {
URL url = e.nextElement();
ConfigOrigin elementOrigin = ((SimpleConfigOrigin) origin).addURL(url);
AbstractConfigValue v;
// it's tempting to use ParseableURL here but it would be wrong
// because the wrong relativeTo() would be used for includes.
InputStream stream = url.openStream();
try {
Reader reader = readerFromStream(stream);
stream = null; // reader now owns it
try {
// parse in "raw" mode which will throw any IOException
// from here.
v = rawParseValue(reader, elementOrigin, finalOptions);
} finally {
reader.close();
}
} finally {
// stream is null if the reader owns it
if (stream != null)
stream.close();
}
merged = merged.withFallback(v);
}
return merged;
} }
@Override @Override
@ -436,48 +449,76 @@ public abstract class Parseable implements ConfigParseable {
return syntaxFromExtension(resource); return syntaxFromExtension(resource);
} }
@Override static String parent(String resource) {
ConfigParseable relativeTo(String filename) { int i = resource.lastIndexOf('/');
// not using File.isAbsolute because resource paths always use '/' if (i < 0) {
// (?)
if (filename.startsWith("/"))
return null; return null;
} else {
return resource.substring(0, i);
}
}
@Override
ConfigParseable relativeTo(String sibling) {
// here we want to build a new resource name and let // here we want to build a new resource name and let
// the class loader have it, rather than getting the // the class loader have it, rather than getting the
// url with getResource() and relativizing to that url. // url with getResource() and relativizing to that url.
// This is needed in case the class loader is going to // This is needed in case the class loader is going to
// search a classpath. // search a classpath.
File parent = new File(resource).getParentFile(); String parent = parent(resource);
if (parent == null) if (parent == null)
return newResource(klass, "/" + filename, options() return newResources(loader, sibling, options()
.setOriginDescription(null)); .setOriginDescription(null));
else else
return newResource(klass, new File(parent, filename).getPath(), return newResources(loader, parent + "/" + sibling,
options().setOriginDescription(null)); options().setOriginDescription(null));
} }
@Override @Override
String originDescription() { protected ConfigOrigin createOrigin() {
return resource + " on classpath"; return SimpleConfigOrigin.newResource(resource);
}
@Override
public URL url() {
return klass.getResource(resource);
} }
@Override @Override
public String toString() { public String toString() {
return getClass().getSimpleName() + "(" + resource + "," return getClass().getSimpleName() + "(" + resource + ","
+ klass.getName() + loader.getClass().getSimpleName() + ")";
+ ")";
} }
} }
public static Parseable newResource(Class<?> klass, String resource, public static Parseable newResources(Class<?> klass, String resource,
ConfigParseOptions options) { ConfigParseOptions options) {
return new ParseableResource(klass, resource, options); return newResources(klass.getClassLoader(), convertResourceName(klass, resource), options);
}
// this function is supposed to emulate the difference
// between Class.getResource and ClassLoader.getResource
// (unfortunately there doesn't seem to be public API for it).
// We're using it because the Class API is more limited,
// for example it lacks getResources(). So we want to be able to
// use ClassLoader directly.
private static String convertResourceName(Class<?> klass, String resource) {
if (resource.startsWith("/")) {
// "absolute" resource, chop the slash
return resource.substring(1);
} else {
String className = klass.getName();
int i = className.lastIndexOf('.');
if (i < 0) {
// no package
return resource;
} else {
// need to be relative to the package
String packageName = className.substring(0, i);
String packagePath = packageName.replace('.', '/');
return packagePath + "/" + resource;
}
}
}
public static Parseable newResources(ClassLoader loader, String resource,
ConfigParseOptions options) {
return new ParseableResources(loader, resource, options);
} }
private final static class ParseableProperties extends Parseable { private final static class ParseableProperties extends Parseable {
@ -495,7 +536,7 @@ public abstract class Parseable implements ConfigParseable {
} }
@Override @Override
protected AbstractConfigObject parseValue(ConfigOrigin origin, protected AbstractConfigObject rawParseValue(ConfigOrigin origin,
ConfigParseOptions finalOptions) { ConfigParseOptions finalOptions) {
return PropertiesParser.fromProperties(origin, props); return PropertiesParser.fromProperties(origin, props);
} }
@ -506,13 +547,8 @@ public abstract class Parseable implements ConfigParseable {
} }
@Override @Override
String originDescription() { protected ConfigOrigin createOrigin() {
return "properties"; return SimpleConfigOrigin.newSimple("properties");
}
@Override
public URL url() {
return null;
} }
@Override @Override

View file

@ -110,7 +110,8 @@ final class Parser {
Token t = nextToken(); Token t = nextToken();
while (true) { while (true) {
if (Tokens.isNewline(t)) { if (Tokens.isNewline(t)) {
lineNumber = Tokens.getLineNumber(t); // newline number is the line just ended, so add one
lineNumber = Tokens.getLineNumber(t) + 1;
sawSeparatorOrNewline = true; sawSeparatorOrNewline = true;
// we want to continue to also eat // we want to continue to also eat
// a comma if there is one. // a comma if there is one.
@ -155,7 +156,7 @@ final class Parser {
return; return;
} }
// this will be a list of String and Path // this will be a list of String and SubstitutionExpression
List<Object> minimized = new ArrayList<Object>(); List<Object> minimized = new ArrayList<Object>();
// we have multiple value tokens or one unquoted text token; // we have multiple value tokens or one unquoted text token;
@ -187,7 +188,9 @@ final class Parser {
.getSubstitutionPathExpression(valueToken); .getSubstitutionPathExpression(valueToken);
Path path = parsePathExpression(expression.iterator(), Path path = parsePathExpression(expression.iterator(),
Tokens.getSubstitutionOrigin(valueToken)); Tokens.getSubstitutionOrigin(valueToken));
minimized.add(path); boolean optional = Tokens.getSubstitutionOptional(valueToken);
minimized.add(new SubstitutionExpression(path, optional));
} else { } else {
throw new ConfigException.BugOrBroken( throw new ConfigException.BugOrBroken(
"should not be trying to consolidate token: " "should not be trying to consolidate token: "
@ -219,8 +222,7 @@ final class Parser {
} }
private ConfigOrigin lineOrigin() { private ConfigOrigin lineOrigin() {
return new SimpleConfigOrigin(baseOrigin.description() + ": line " return ((SimpleConfigOrigin) baseOrigin).setLineNumber(lineNumber);
+ lineNumber);
} }
private ConfigException parseError(String message) { private ConfigException parseError(String message) {
@ -681,7 +683,7 @@ final class Parser {
return pb.result(); return pb.result();
} }
static ConfigOrigin apiOrigin = new SimpleConfigOrigin("path parameter"); static ConfigOrigin apiOrigin = SimpleConfigOrigin.newSimple("path parameter");
static Path parsePath(String path) { static Path parsePath(String path) {
Path speculated = speculativeFastParsePath(path); Path speculated = speculativeFastParsePath(path);

View file

@ -152,7 +152,7 @@ final class Path {
} }
private void appendToStringBuilder(StringBuilder sb) { private void appendToStringBuilder(StringBuilder sb) {
if (hasFunkyChars(first)) if (hasFunkyChars(first) || first.isEmpty())
sb.append(ConfigUtil.renderJsonString(first)); sb.append(ConfigUtil.renderJsonString(first));
else else
sb.append(first); sb.append(first);

View file

@ -1,61 +0,0 @@
/**
* Copyright (C) 2011 Typesafe Inc. <http://typesafe.com>
*/
package com.typesafe.config.impl;
import com.typesafe.config.ConfigMergeable;
import com.typesafe.config.ConfigResolveOptions;
import com.typesafe.config.ConfigRoot;
final class RootConfig extends SimpleConfig implements ConfigRoot {
final private Path rootPath;
RootConfig(AbstractConfigObject underlying, Path rootPath) {
super(underlying);
this.rootPath = rootPath;
}
@Override
protected RootConfig asRoot(AbstractConfigObject underlying,
Path newRootPath) {
if (newRootPath.equals(this.rootPath))
return this;
else
return new RootConfig(underlying, newRootPath);
}
@Override
public RootConfig resolve() {
return resolve(ConfigResolveOptions.defaults());
}
@Override
public RootConfig resolve(ConfigResolveOptions options) {
// if the object is already resolved then we should end up returning
// "this" here, since asRoot() should return this if the path
// is unchanged.
AbstractConfigObject resolved = resolvedObject(options);
return newRootIfObjectChanged(this, resolved);
}
@Override
public RootConfig withFallback(ConfigMergeable value) {
// this can return "this" if the withFallback does nothing
return newRootIfObjectChanged(this, super.withFallback(value).toObject());
}
Path rootPathObject() {
return rootPath;
}
@Override
public String rootPath() {
return rootPath.render();
}
@Override
public String toString() {
return "Root" + super.toString();
}
}

View file

@ -4,7 +4,9 @@
package com.typesafe.config.impl; package com.typesafe.config.impl;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import com.typesafe.config.Config; import com.typesafe.config.Config;
@ -34,7 +36,7 @@ class SimpleConfig implements Config {
} }
@Override @Override
public AbstractConfigObject toObject() { public AbstractConfigObject root() {
return object; return object;
} }
@ -43,35 +45,22 @@ class SimpleConfig implements Config {
return object.origin(); return object.origin();
} }
/** @Override
* Returns a version of this config that implements the ConfigRoot public SimpleConfig resolve() {
* interface. return resolve(ConfigResolveOptions.defaults());
*
* @return a config root
*/
RootConfig asRoot(Path rootPath) {
return asRoot(object, rootPath);
} }
// RootConfig overrides this to avoid a new object on unchanged path. @Override
protected RootConfig asRoot(AbstractConfigObject underlying, public SimpleConfig resolve(ConfigResolveOptions options) {
Path newRootPath) {
return new RootConfig(underlying, newRootPath);
}
static protected RootConfig newRootIfObjectChanged(RootConfig self, AbstractConfigObject underlying) {
if (underlying == self.object)
return self;
else
return new RootConfig(underlying, self.rootPathObject());
}
protected AbstractConfigObject resolvedObject(ConfigResolveOptions options) {
AbstractConfigValue resolved = SubstitutionResolver.resolve(object, AbstractConfigValue resolved = SubstitutionResolver.resolve(object,
object, options); object, options);
return (AbstractConfigObject) resolved; if (resolved == object)
return this;
else
return new SimpleConfig((AbstractConfigObject) resolved);
} }
@Override @Override
public boolean hasPath(String pathExpression) { public boolean hasPath(String pathExpression) {
Path path = Path.newPath(pathExpression); Path path = Path.newPath(pathExpression);
@ -196,13 +185,13 @@ class SimpleConfig implements Config {
} }
@Override @Override
public Long getMemorySizeInBytes(String path) { public Long getBytes(String path) {
Long size = null; Long size = null;
try { try {
size = getLong(path); size = getLong(path);
} catch (ConfigException.WrongType e) { } catch (ConfigException.WrongType e) {
ConfigValue v = find(path, ConfigValueType.STRING, path); ConfigValue v = find(path, ConfigValueType.STRING, path);
size = parseMemorySizeInBytes((String) v.unwrapped(), size = parseBytes((String) v.unwrapped(),
v.origin(), path); v.origin(), path);
} }
return size; return size;
@ -346,7 +335,7 @@ class SimpleConfig implements Config {
l.add(((Number) v.unwrapped()).longValue()); l.add(((Number) v.unwrapped()).longValue());
} else if (v.valueType() == ConfigValueType.STRING) { } else if (v.valueType() == ConfigValueType.STRING) {
String s = (String) v.unwrapped(); String s = (String) v.unwrapped();
Long n = parseMemorySizeInBytes(s, v.origin(), path); Long n = parseBytes(s, v.origin(), path);
l.add(n); l.add(n);
} else { } else {
throw new ConfigException.WrongType(v.origin(), path, throw new ConfigException.WrongType(v.origin(), path,
@ -509,23 +498,87 @@ class SimpleConfig implements Config {
} }
private static enum MemoryUnit { private static enum MemoryUnit {
BYTES(1), KILOBYTES(1024), MEGABYTES(1024 * 1024), GIGABYTES( BYTES("", 1024, 0),
1024 * 1024 * 1024), TERABYTES(1024 * 1024 * 1024 * 1024);
int bytes; KILOBYTES("kilo", 1000, 1),
MEGABYTES("mega", 1000, 2),
GIGABYTES("giga", 1000, 3),
TERABYTES("tera", 1000, 4),
PETABYTES("peta", 1000, 5),
EXABYTES("exa", 1000, 6),
ZETTABYTES("zetta", 1000, 7),
YOTTABYTES("yotta", 1000, 8),
MemoryUnit(int bytes) { KIBIBYTES("kibi", 1024, 1),
MEBIBYTES("mebi", 1024, 2),
GIBIBYTES("gibi", 1024, 3),
TEBIBYTES("tebi", 1024, 4),
PEBIBYTES("pebi", 1024, 5),
EXBIBYTES("exbi", 1024, 6),
ZEBIBYTES("zebi", 1024, 7),
YOBIBYTES("yobi", 1024, 8);
final String prefix;
final int powerOf;
final int power;
final long bytes;
MemoryUnit(String prefix, int powerOf, int power) {
this.prefix = prefix;
this.powerOf = powerOf;
this.power = power;
int i = power;
long bytes = 1;
while (i > 0) {
bytes *= powerOf;
--i;
}
this.bytes = bytes; this.bytes = bytes;
} }
private static Map<String, MemoryUnit> makeUnitsMap() {
Map<String, MemoryUnit> map = new HashMap<String, MemoryUnit>();
for (MemoryUnit unit : MemoryUnit.values()) {
map.put(unit.prefix + "byte", unit);
map.put(unit.prefix + "bytes", unit);
if (unit.prefix.length() == 0) {
map.put("b", unit);
map.put("B", unit);
map.put("", unit); // no unit specified means bytes
} else {
String first = unit.prefix.substring(0, 1);
String firstUpper = first.toUpperCase();
if (unit.powerOf == 1024) {
map.put(first, unit); // 512m
map.put(firstUpper, unit); // 512M
map.put(firstUpper + "i", unit); // 512Mi
map.put(firstUpper + "iB", unit); // 512MiB
} else if (unit.powerOf == 1000) {
if (unit.power == 1) {
map.put(first + "B", unit); // 512kB
} else {
map.put(firstUpper + "B", unit); // 512MB
}
} else {
throw new RuntimeException("broken MemoryUnit enum");
}
}
}
return map;
}
private static Map<String, MemoryUnit> unitsMap = makeUnitsMap();
static MemoryUnit parseUnit(String unit) {
return unitsMap.get(unit);
}
} }
/** /**
* Parses a memory-size string. If no units are specified in the string, it * Parses a size-in-bytes string. If no units are specified in the string,
* is assumed to be in bytes. The returned value is in bytes. The purpose of * it is assumed to be in bytes. The returned value is in bytes. The purpose
* this function is to implement the memory-size-related methods in the * of this function is to implement the size-in-bytes-related methods in the
* ConfigObject interface. The units parsed are interpreted as powers of * Config interface.
* two, that is, the convention for memory rather than the convention for
* disk space.
* *
* @param input * @param input
* the string to parse * the string to parse
@ -537,19 +590,12 @@ class SimpleConfig implements Config {
* @throws ConfigException * @throws ConfigException
* if string is invalid * if string is invalid
*/ */
public static long parseMemorySizeInBytes(String input, public static long parseBytes(String input, ConfigOrigin originForException,
ConfigOrigin originForException, String pathForException) { String pathForException) {
String s = ConfigUtil.unicodeTrim(input); String s = ConfigUtil.unicodeTrim(input);
String unitStringMaybePlural = getUnits(s); String unitString = getUnits(s);
String unitString; String numberString = ConfigUtil.unicodeTrim(s.substring(0,
if (unitStringMaybePlural.endsWith("s")) s.length() - unitString.length()));
unitString = unitStringMaybePlural.substring(0,
unitStringMaybePlural.length() - 1);
else
unitString = unitStringMaybePlural;
String unitStringLower = unitString.toLowerCase();
String numberString = ConfigUtil.unicodeTrim(s.substring(0, s.length()
- unitStringMaybePlural.length()));
// this would be caught later anyway, but the error message // this would be caught later anyway, but the error message
// is more helpful if we check it here. // is more helpful if we check it here.
@ -558,40 +604,197 @@ class SimpleConfig implements Config {
pathForException, "No number in size-in-bytes value '" pathForException, "No number in size-in-bytes value '"
+ input + "'"); + input + "'");
MemoryUnit units = null; MemoryUnit units = MemoryUnit.parseUnit(unitString);
// the short abbreviations are case-insensitive but you can't write the if (units == null) {
// long form words in all caps. throw new ConfigException.BadValue(originForException, pathForException,
if (unitString.equals("") || unitStringLower.equals("b") "Could not parse size-in-bytes unit '" + unitString
|| unitString.equals("byte")) { + "' (try k, K, kB, KiB, kilobytes, kibibytes)");
units = MemoryUnit.BYTES;
} else if (unitStringLower.equals("k") || unitString.equals("kilobyte")) {
units = MemoryUnit.KILOBYTES;
} else if (unitStringLower.equals("m") || unitString.equals("megabyte")) {
units = MemoryUnit.MEGABYTES;
} else if (unitStringLower.equals("g") || unitString.equals("gigabyte")) {
units = MemoryUnit.GIGABYTES;
} else if (unitStringLower.equals("t") || unitString.equals("terabyte")) {
units = MemoryUnit.TERABYTES;
} else {
throw new ConfigException.BadValue(originForException,
pathForException, "Could not parse size unit '"
+ unitStringMaybePlural + "' (try b, k, m, g, t)");
} }
try { try {
// if the string is purely digits, parse as an integer to avoid // if the string is purely digits, parse as an integer to avoid
// possible precision loss; // possible precision loss; otherwise as a double.
// otherwise as a double.
if (numberString.matches("[0-9]+")) { if (numberString.matches("[0-9]+")) {
return Long.parseLong(numberString) * units.bytes; return Long.parseLong(numberString) * units.bytes;
} else { } else {
return (long) (Double.parseDouble(numberString) * units.bytes); return (long) (Double.parseDouble(numberString) * units.bytes);
} }
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
throw new ConfigException.BadValue(originForException, throw new ConfigException.BadValue(originForException, pathForException,
pathForException, "Could not parse memory size number '" "Could not parse size-in-bytes number '" + numberString + "'");
+ numberString + "'"); }
}
private AbstractConfigValue peekPath(Path path) {
return root().peekPath(path);
}
private static void addProblem(List<ConfigException.ValidationProblem> accumulator, Path path,
ConfigOrigin origin, String problem) {
accumulator.add(new ConfigException.ValidationProblem(path.render(), origin, problem));
}
private static String getDesc(ConfigValue refValue) {
if (refValue instanceof AbstractConfigObject) {
AbstractConfigObject obj = (AbstractConfigObject) refValue;
if (obj.isEmpty())
return "object";
else
return "object with keys " + obj.keySet();
} else if (refValue instanceof SimpleConfigList) {
return "list";
} else {
return refValue.valueType().name().toLowerCase();
}
}
private static void addMissing(List<ConfigException.ValidationProblem> accumulator,
ConfigValue refValue, Path path, ConfigOrigin origin) {
addProblem(accumulator, path, origin, "No setting at '" + path.render() + "', expecting: "
+ getDesc(refValue));
}
private static void addWrongType(List<ConfigException.ValidationProblem> accumulator,
ConfigValue refValue, AbstractConfigValue actual, Path path) {
addProblem(accumulator, path, actual.origin(), "Wrong value type at '" + path.render()
+ "', expecting: " + getDesc(refValue) + " but got: "
+ getDesc(actual));
}
private static boolean couldBeNull(AbstractConfigValue v) {
return DefaultTransformer.transform(v, ConfigValueType.NULL)
.valueType() == ConfigValueType.NULL;
}
private static boolean haveCompatibleTypes(ConfigValue reference, AbstractConfigValue value) {
if (couldBeNull((AbstractConfigValue) reference) || couldBeNull(value)) {
// we allow any setting to be null
return true;
} else if (reference instanceof AbstractConfigObject) {
if (value instanceof AbstractConfigObject) {
return true;
} else {
return false;
}
} else if (reference instanceof SimpleConfigList) {
if (value instanceof SimpleConfigList) {
return true;
} else {
return false;
}
} else if (reference instanceof ConfigString) {
// assume a string could be gotten as any non-collection type;
// allows things like getMilliseconds including domain-specific
// interpretations of strings
return true;
} else if (value instanceof ConfigString) {
// assume a string could be gotten as any non-collection type
return true;
} else {
if (reference.valueType() == value.valueType()) {
return true;
} else {
return false;
}
}
}
// path is null if we're at the root
private static void checkValidObject(Path path, AbstractConfigObject reference,
AbstractConfigObject value,
List<ConfigException.ValidationProblem> accumulator) {
for (Map.Entry<String, ConfigValue> entry : reference.entrySet()) {
String key = entry.getKey();
Path childPath;
if (path != null)
childPath = Path.newKey(key).prepend(path);
else
childPath = Path.newKey(key);
AbstractConfigValue v = value.get(key);
if (v == null) {
addMissing(accumulator, entry.getValue(), childPath, value.origin());
} else {
checkValid(childPath, entry.getValue(), v, accumulator);
}
}
}
private static void checkValid(Path path, ConfigValue reference, AbstractConfigValue value,
List<ConfigException.ValidationProblem> accumulator) {
// Unmergeable is supposed to be impossible to encounter in here
// because we check for resolve status up front.
if (haveCompatibleTypes(reference, value)) {
if (reference instanceof AbstractConfigObject && value instanceof AbstractConfigObject) {
checkValidObject(path, (AbstractConfigObject) reference,
(AbstractConfigObject) value, accumulator);
} else if (reference instanceof SimpleConfigList && value instanceof SimpleConfigList) {
SimpleConfigList listRef = (SimpleConfigList) reference;
SimpleConfigList listValue = (SimpleConfigList) value;
if (listRef.isEmpty() || listValue.isEmpty()) {
// can't verify type, leave alone
} else {
AbstractConfigValue refElement = listRef.get(0);
for (ConfigValue elem : listValue) {
AbstractConfigValue e = (AbstractConfigValue) elem;
if (!haveCompatibleTypes(refElement, e)) {
addProblem(accumulator, path, e.origin(), "List at '" + path.render()
+ "' contains wrong value type, expecting list of "
+ getDesc(refElement) + " but got element of type "
+ getDesc(e));
// don't add a problem for every last array element
break;
}
}
}
}
} else {
addWrongType(accumulator, reference, value, path);
}
}
@Override
public void checkValid(Config reference, String... restrictToPaths) {
SimpleConfig ref = (SimpleConfig) reference;
// unresolved reference config is a bug in the caller of checkValid
if (ref.root().resolveStatus() != ResolveStatus.RESOLVED)
throw new ConfigException.BugOrBroken(
"do not call checkValid() with an unresolved reference config, call Config.resolve()");
// unresolved config under validation is probably a bug in something,
// but our whole goal here is to check for bugs in this config, so
// BugOrBroken is not the appropriate exception.
if (root().resolveStatus() != ResolveStatus.RESOLVED)
throw new ConfigException.NotResolved(
"config has unresolved substitutions; must call Config.resolve()");
// Now we know that both reference and this config are resolved
List<ConfigException.ValidationProblem> problems = new ArrayList<ConfigException.ValidationProblem>();
if (restrictToPaths.length == 0) {
checkValidObject(null, ref.root(), root(), problems);
} else {
for (String p : restrictToPaths) {
Path path = Path.newPath(p);
AbstractConfigValue refValue = ref.peekPath(path);
if (refValue != null) {
AbstractConfigValue child = peekPath(path);
if (child != null) {
checkValid(path, refValue, child, problems);
} else {
addMissing(problems, refValue, path, origin());
}
}
}
}
if (!problems.isEmpty()) {
throw new ConfigException.ValidationFailed(problems);
} }
} }
} }

View file

@ -9,7 +9,6 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.ListIterator; import java.util.ListIterator;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigList; import com.typesafe.config.ConfigList;
import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigResolveOptions; import com.typesafe.config.ConfigResolveOptions;
@ -68,8 +67,9 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList {
} }
// once the new list is created, all elements // once the new list is created, all elements
// have to go in it. // have to go in it. if modifyChild returned
if (changed != null) { // null, we drop that element.
if (changed != null && modified != null) {
changed.add(modified); changed.add(modified);
} }
@ -77,9 +77,6 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList {
} }
if (changed != null) { if (changed != null) {
if (changed.size() != value.size())
throw new ConfigException.BugOrBroken(
"substituted list's size doesn't match");
return new SimpleConfigList(origin(), changed, newResolveStatus); return new SimpleConfigList(origin(), changed, newResolveStatus);
} else { } else {
return this; return this;
@ -135,18 +132,34 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList {
} }
@Override @Override
public String toString() { protected void render(StringBuilder sb, int indent, boolean formatted) {
StringBuilder sb = new StringBuilder(); if (value.isEmpty()) {
sb.append(valueType().name()); sb.append("[]");
sb.append("("); } else {
for (ConfigValue e : value) { sb.append("[");
sb.append(e.toString()); if (formatted)
sb.append(","); sb.append('\n');
for (AbstractConfigValue v : value) {
if (formatted) {
indent(sb, indent + 1);
sb.append("# ");
sb.append(v.origin().description());
sb.append("\n");
indent(sb, indent + 1);
}
v.render(sb, indent + 1, formatted);
sb.append(",");
if (formatted)
sb.append('\n');
}
sb.setLength(sb.length() - 1); // chop or newline
if (formatted) {
sb.setLength(sb.length() - 1); // also chop comma
sb.append('\n');
indent(sb, indent);
}
sb.append("]");
} }
if (!value.isEmpty())
sb.setLength(sb.length() - 1); // chop comma
sb.append(")");
return sb.toString();
} }
@Override @Override
@ -160,7 +173,7 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList {
} }
@Override @Override
public ConfigValue get(int index) { public AbstractConfigValue get(int index) {
return value.get(index); return value.get(index);
} }

View file

@ -112,8 +112,8 @@ final class SimpleConfigObject extends AbstractConfigObject {
} }
final private static String EMPTY_NAME = "empty config"; final private static String EMPTY_NAME = "empty config";
final private static SimpleConfigObject emptyInstance = empty(new SimpleConfigOrigin( final private static SimpleConfigObject emptyInstance = empty(SimpleConfigOrigin
EMPTY_NAME)); .newSimple(EMPTY_NAME));
final static SimpleConfigObject empty() { final static SimpleConfigObject empty() {
return emptyInstance; return emptyInstance;
@ -128,7 +128,7 @@ final class SimpleConfigObject extends AbstractConfigObject {
} }
final static SimpleConfigObject emptyMissing(ConfigOrigin baseOrigin) { final static SimpleConfigObject emptyMissing(ConfigOrigin baseOrigin) {
return new SimpleConfigObject(new SimpleConfigOrigin( return new SimpleConfigObject(SimpleConfigOrigin.newSimple(
baseOrigin.description() + " (not found)"), baseOrigin.description() + " (not found)"),
Collections.<String, AbstractConfigValue> emptyMap()); Collections.<String, AbstractConfigValue> emptyMap());
} }

View file

@ -3,26 +3,101 @@
*/ */
package com.typesafe.config.impl; package com.typesafe.config.impl;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigOrigin;
// it would be cleaner to have a class hierarchy for various origin types,
// but was hoping this would be enough simpler to be a little messy. eh.
final class SimpleConfigOrigin implements ConfigOrigin { final class SimpleConfigOrigin implements ConfigOrigin {
final private String description; final private String description;
final private int lineNumber;
final private int endLineNumber;
final private OriginType originType;
final private String urlOrNull;
SimpleConfigOrigin(String description) { protected SimpleConfigOrigin(String description, int lineNumber, int endLineNumber,
OriginType originType,
String urlOrNull) {
this.description = description; this.description = description;
this.lineNumber = lineNumber;
this.endLineNumber = endLineNumber;
this.originType = originType;
this.urlOrNull = urlOrNull;
}
static SimpleConfigOrigin newSimple(String description) {
return new SimpleConfigOrigin(description, -1, -1, OriginType.GENERIC, null);
}
static SimpleConfigOrigin newFile(String filename) {
String url;
try {
url = (new File(filename)).toURI().toURL().toExternalForm();
} catch (MalformedURLException e) {
url = null;
}
return new SimpleConfigOrigin(filename, -1, -1, OriginType.FILE, url);
}
static SimpleConfigOrigin newURL(URL url) {
String u = url.toExternalForm();
return new SimpleConfigOrigin(u, -1, -1, OriginType.URL, u);
}
static SimpleConfigOrigin newResource(String resource, URL url) {
return new SimpleConfigOrigin(resource, -1, -1, OriginType.RESOURCE,
url != null ? url.toExternalForm() : null);
}
static SimpleConfigOrigin newResource(String resource) {
return newResource(resource, null);
}
SimpleConfigOrigin setLineNumber(int lineNumber) {
if (lineNumber == this.lineNumber && lineNumber == this.endLineNumber) {
return this;
} else {
return new SimpleConfigOrigin(this.description, lineNumber, lineNumber,
this.originType, this.urlOrNull);
}
}
SimpleConfigOrigin addURL(URL url) {
return new SimpleConfigOrigin(this.description, this.lineNumber, this.endLineNumber, this.originType,
url != null ? url.toExternalForm() : null);
} }
@Override @Override
public String description() { public String description() {
return description; // not putting the URL in here for files and resources, because people
// parsing "file: line" syntax would hit the ":" in the URL.
if (lineNumber < 0) {
return description;
} else if (endLineNumber == lineNumber) {
return description + ": " + lineNumber;
} else {
return description + ": " + lineNumber + "-" + endLineNumber;
}
} }
@Override @Override
public boolean equals(Object other) { public boolean equals(Object other) {
if (other instanceof SimpleConfigOrigin) { if (other instanceof SimpleConfigOrigin) {
return this.description SimpleConfigOrigin otherOrigin = (SimpleConfigOrigin) other;
.equals(((SimpleConfigOrigin) other).description);
return this.description.equals(otherOrigin.description)
&& this.lineNumber == otherOrigin.lineNumber
&& this.endLineNumber == otherOrigin.endLineNumber
&& this.originType == otherOrigin.originType
&& ConfigUtil.equalsHandlingNull(this.urlOrNull, otherOrigin.urlOrNull);
} else { } else {
return false; return false;
} }
@ -30,11 +105,201 @@ final class SimpleConfigOrigin implements ConfigOrigin {
@Override @Override
public int hashCode() { public int hashCode() {
return description.hashCode(); int h = 41 * (41 + description.hashCode());
h = 41 * (h + lineNumber);
h = 41 * (h + endLineNumber);
h = 41 * (h + originType.hashCode());
if (urlOrNull != null)
h = 41 * (h + urlOrNull.hashCode());
return h;
} }
@Override @Override
public String toString() { public String toString() {
return "ConfigOrigin(" + description + ")"; // the url is only really useful on top of description for resources
if (originType == OriginType.RESOURCE && urlOrNull != null) {
return "ConfigOrigin(" + description + "," + urlOrNull + ")";
} else {
return "ConfigOrigin(" + description + ")";
}
}
@Override
public String filename() {
if (originType == OriginType.FILE) {
return description;
} else if (urlOrNull != null) {
URL url;
try {
url = new URL(urlOrNull);
} catch (MalformedURLException e) {
return null;
}
if (url.getProtocol().equals("file")) {
return url.getFile();
} else {
return null;
}
} else {
return null;
}
}
@Override
public URL url() {
if (urlOrNull == null) {
return null;
} else {
try {
return new URL(urlOrNull);
} catch (MalformedURLException e) {
return null;
}
}
}
@Override
public String resource() {
if (originType == OriginType.RESOURCE) {
return description;
} else {
return null;
}
}
@Override
public int lineNumber() {
return lineNumber;
}
static final String MERGE_OF_PREFIX = "merge of ";
private static SimpleConfigOrigin mergeTwo(SimpleConfigOrigin a, SimpleConfigOrigin b) {
String mergedDesc;
int mergedStartLine;
int mergedEndLine;
OriginType mergedType;
if (a.originType == b.originType) {
mergedType = a.originType;
} else {
mergedType = OriginType.GENERIC;
}
// first use the "description" field which has no line numbers
// cluttering it.
String aDesc = a.description;
String bDesc = b.description;
if (aDesc.startsWith(MERGE_OF_PREFIX))
aDesc = aDesc.substring(MERGE_OF_PREFIX.length());
if (bDesc.startsWith(MERGE_OF_PREFIX))
bDesc = bDesc.substring(MERGE_OF_PREFIX.length());
if (aDesc.equals(bDesc)) {
mergedDesc = aDesc;
if (a.lineNumber < 0)
mergedStartLine = b.lineNumber;
else if (b.lineNumber < 0)
mergedStartLine = a.lineNumber;
else
mergedStartLine = Math.min(a.lineNumber, b.lineNumber);
mergedEndLine = Math.max(a.endLineNumber, b.endLineNumber);
} else {
// this whole merge song-and-dance was intended to avoid this case
// whenever possible, but we've lost. Now we have to lose some
// structured information and cram into a string.
// description() method includes line numbers, so use it instead
// of description field.
String aFull = a.description();
String bFull = b.description();
if (aFull.startsWith(MERGE_OF_PREFIX))
aFull = aFull.substring(MERGE_OF_PREFIX.length());
if (bFull.startsWith(MERGE_OF_PREFIX))
bFull = bFull.substring(MERGE_OF_PREFIX.length());
mergedDesc = MERGE_OF_PREFIX + aFull + "," + bFull;
mergedStartLine = -1;
mergedEndLine = -1;
}
String mergedURL;
if (ConfigUtil.equalsHandlingNull(a.urlOrNull, b.urlOrNull)) {
mergedURL = a.urlOrNull;
} else {
mergedURL = null;
}
return new SimpleConfigOrigin(mergedDesc, mergedStartLine, mergedEndLine, mergedType,
mergedURL);
}
private static int similarity(SimpleConfigOrigin a, SimpleConfigOrigin b) {
int count = 0;
if (a.originType == b.originType)
count += 1;
if (a.description.equals(b.description)) {
count += 1;
// only count these if the description field (which is the file
// or resource name) also matches.
if (a.lineNumber == b.lineNumber)
count += 1;
if (a.endLineNumber == b.endLineNumber)
count += 1;
if (ConfigUtil.equalsHandlingNull(a.urlOrNull, b.urlOrNull))
count += 1;
}
return count;
}
// this picks the best pair to merge, because the pair has the most in
// common. we want to merge two lines in the same file rather than something
// else with one of the lines; because two lines in the same file can be
// better consolidated.
private static SimpleConfigOrigin mergeThree(SimpleConfigOrigin a, SimpleConfigOrigin b,
SimpleConfigOrigin c) {
if (similarity(a, b) >= similarity(b, c)) {
return mergeTwo(mergeTwo(a, b), c);
} else {
return mergeTwo(a, mergeTwo(b, c));
}
}
static ConfigOrigin mergeOrigins(Collection<? extends ConfigOrigin> stack) {
if (stack.isEmpty()) {
throw new ConfigException.BugOrBroken("can't merge empty list of origins");
} else if (stack.size() == 1) {
return stack.iterator().next();
} else if (stack.size() == 2) {
Iterator<? extends ConfigOrigin> i = stack.iterator();
return mergeTwo((SimpleConfigOrigin) i.next(), (SimpleConfigOrigin) i.next());
} else {
List<SimpleConfigOrigin> remaining = new ArrayList<SimpleConfigOrigin>();
for (ConfigOrigin o : stack) {
remaining.add((SimpleConfigOrigin) o);
}
while (remaining.size() > 2) {
SimpleConfigOrigin c = remaining.get(remaining.size() - 1);
remaining.remove(remaining.size() - 1);
SimpleConfigOrigin b = remaining.get(remaining.size() - 1);
remaining.remove(remaining.size() - 1);
SimpleConfigOrigin a = remaining.get(remaining.size() - 1);
remaining.remove(remaining.size() - 1);
SimpleConfigOrigin merged = mergeThree(a, b, c);
remaining.add(merged);
}
// should be down to either 1 or 2
return mergeOrigins(remaining);
}
} }
} }

View file

@ -0,0 +1,46 @@
package com.typesafe.config.impl;
final class SubstitutionExpression {
final private Path path;
final private boolean optional;
SubstitutionExpression(Path path, boolean optional) {
this.path = path;
this.optional = optional;
}
Path path() {
return path;
}
boolean optional() {
return optional;
}
SubstitutionExpression changePath(Path newPath) {
return new SubstitutionExpression(newPath, optional);
}
@Override
public String toString() {
return "${" + (optional ? "?" : "") + path.render() + "}";
}
@Override
public boolean equals(Object other) {
if (other instanceof SubstitutionExpression) {
SubstitutionExpression otherExp = (SubstitutionExpression) other;
return otherExp.path.equals(this.path) && otherExp.optional == this.optional;
} else {
return false;
}
}
@Override
public int hashCode() {
int h = 41 * (41 + path.hashCode());
h = 41 * (h + (optional ? 1 : 0));
return h;
}
}

View file

@ -16,6 +16,8 @@ import com.typesafe.config.ConfigResolveOptions;
*/ */
final class SubstitutionResolver { final class SubstitutionResolver {
final private AbstractConfigObject root; final private AbstractConfigObject root;
// note that we can resolve things to undefined (represented as Java null,
// rather than ConfigNull) so this map can have null values.
final private Map<AbstractConfigValue, AbstractConfigValue> memos; final private Map<AbstractConfigValue, AbstractConfigValue> memos;
SubstitutionResolver(AbstractConfigObject root) { SubstitutionResolver(AbstractConfigObject root) {
@ -31,9 +33,11 @@ final class SubstitutionResolver {
} else { } else {
AbstractConfigValue resolved = original.resolveSubstitutions(this, AbstractConfigValue resolved = original.resolveSubstitutions(this,
depth, options); depth, options);
if (resolved.resolveStatus() != ResolveStatus.RESOLVED) if (resolved != null) {
throw new ConfigException.BugOrBroken( if (resolved.resolveStatus() != ResolveStatus.RESOLVED)
"resolveSubstitutions() did not give us a resolved object"); throw new ConfigException.BugOrBroken(
"resolveSubstitutions() did not give us a resolved object");
}
memos.put(original, resolved); memos.put(original, resolved);
return resolved; return resolved;
} }

View file

@ -219,8 +219,7 @@ final class Tokenizer {
private static ConfigOrigin lineOrigin(ConfigOrigin baseOrigin, private static ConfigOrigin lineOrigin(ConfigOrigin baseOrigin,
int lineNumber) { int lineNumber) {
return new SimpleConfigOrigin(baseOrigin.description() + ": line " return ((SimpleConfigOrigin) baseOrigin).setLineNumber(lineNumber);
+ lineNumber);
} }
// chars JSON allows a number to start with // chars JSON allows a number to start with
@ -228,7 +227,7 @@ final class Tokenizer {
// chars JSON allows to be part of a number // chars JSON allows to be part of a number
static final String numberChars = "0123456789eE+-."; static final String numberChars = "0123456789eE+-.";
// chars that stop an unquoted string // chars that stop an unquoted string
static final String notInUnquotedText = "$\"{}[]:=,\\+#"; static final String notInUnquotedText = "$\"{}[]:=,+#`^?!@*&\\";
// The rules here are intended to maximize convenience while // The rules here are intended to maximize convenience while
// avoiding confusion with real valid JSON. Basically anything // avoiding confusion with real valid JSON. Basically anything
@ -404,6 +403,14 @@ final class Tokenizer {
throw parseError("'$' not followed by {"); throw parseError("'$' not followed by {");
} }
boolean optional = false;
c = nextCharSkippingComments();
if (c == '?') {
optional = true;
} else {
putBack(c);
}
WhitespaceSaver saver = new WhitespaceSaver(); WhitespaceSaver saver = new WhitespaceSaver();
List<Token> expression = new ArrayList<Token>(); List<Token> expression = new ArrayList<Token>();
@ -428,7 +435,7 @@ final class Tokenizer {
} }
} while (true); } while (true);
return Tokens.newSubstitution(origin, expression); return Tokens.newSubstitution(origin, optional, expression);
} }
private Token pullNextToken(WhitespaceSaver saver) { private Token pullNextToken(WhitespaceSaver saver) {

View file

@ -125,11 +125,13 @@ final class Tokens {
// This is not a Value, because it requires special processing // This is not a Value, because it requires special processing
static private class Substitution extends Token { static private class Substitution extends Token {
final private ConfigOrigin origin; final private ConfigOrigin origin;
final private boolean optional;
final private List<Token> value; final private List<Token> value;
Substitution(ConfigOrigin origin, List<Token> expression) { Substitution(ConfigOrigin origin, boolean optional, List<Token> expression) {
super(TokenType.SUBSTITUTION); super(TokenType.SUBSTITUTION);
this.origin = origin; this.origin = origin;
this.optional = optional;
this.value = expression; this.value = expression;
} }
@ -137,6 +139,10 @@ final class Tokens {
return origin; return origin;
} }
boolean optional() {
return optional;
}
List<Token> value() { List<Token> value() {
return value; return value;
} }
@ -237,6 +243,15 @@ final class Tokens {
} }
} }
static boolean getSubstitutionOptional(Token token) {
if (token instanceof Substitution) {
return ((Substitution) token).optional();
} else {
throw new ConfigException.BugOrBroken("tried to get substitution optionality from "
+ token);
}
}
final static Token START = new Token(TokenType.START); final static Token START = new Token(TokenType.START);
final static Token END = new Token(TokenType.END); final static Token END = new Token(TokenType.END);
final static Token COMMA = new Token(TokenType.COMMA); final static Token COMMA = new Token(TokenType.COMMA);
@ -255,8 +270,8 @@ final class Tokens {
return new UnquotedText(origin, s); return new UnquotedText(origin, s);
} }
static Token newSubstitution(ConfigOrigin origin, List<Token> expression) { static Token newSubstitution(ConfigOrigin origin, boolean optional, List<Token> expression) {
return new Substitution(origin, expression); return new Substitution(origin, optional, expression);
} }
static Token newValue(AbstractConfigValue value) { static Token newValue(AbstractConfigValue value) {

View file

@ -0,0 +1,42 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html>
<head>
<!--
Copyright (C) 2011 Typesafe Inc. <http://typesafe.com>
-->
</head>
<body bgcolor="white">
<p>
An API for loading and using configuration files, see <a href="https://github.com/havocp/config/">the project site</a>
for more information.
</p>
<p>
Typically you would load configuration with a static method from {@link com.typesafe.config.ConfigFactory} and then use
it with methods in the {@link com.typesafe.config.Config} interface.
</p>
<p>
An application can simply call {@link com.typesafe.config.ConfigFactory#load()} and place
its configuration in "application.conf" on the classpath.
If you use the default configuration from {@link com.typesafe.config.ConfigFactory#load()}
there's no need to pass a configuration to your libraries
and frameworks, as long as they all default to this same default, which they should.
</p>
<p>
A library or framework should ship a file "reference.conf" in its jar, and allow an application to pass in a
{@link com.typesafe.config.Config} to be used for the library. If no {@link com.typesafe.config.Config} is provided,
call {@link com.typesafe.config.ConfigFactory#load()}
to get the default one. Typically a library might offer two constructors, one with a <code>Config</code> parameter
and one which uses {@link com.typesafe.config.ConfigFactory#load()}.
</p>
<p>
You can find an example app and library <a href="https://github.com/havocp/config/tree/master/examples">on GitHub</a>.
</p>
</body>
</html>

View file

@ -1,32 +0,0 @@
############################################
# Akka Serialization Reference Config File #
############################################
# This the reference config file has all the default settings.
# Make your edits/overrides in your akka.conf.
akka {
actor {
# Entries for pluggable serializers and their bindings. If a binding for a specific class is not found,
# then the default serializer (Java serialization) is used.
#
serializers {
# java = "akka.serialization.JavaSerializer"
# proto = "akka.testing.ProtobufSerializer"
# sjson = "akka.testing.SJSONSerializer"
default = "akka.serialization.JavaSerializer"
}
# serialization-bindings {
# java = ["akka.serialization.SerializeSpec$Address",
# "akka.serialization.MyJavaSerializableActor",
# "akka.serialization.MyStatelessActorWithMessagesInMailbox",
# "akka.serialization.MyActorWithProtobufMessagesInMailbox"]
# sjson = ["akka.serialization.SerializeSpec$Person"]
# proto = ["com.google.protobuf.Message",
# "akka.actor.ProtobufProtocol$MyMessage"]
# }
}
}

View file

@ -16,7 +16,7 @@ akka {
loglevel = "INFO" # Options: ERROR, WARNING, INFO, DEBUG loglevel = "INFO" # Options: ERROR, WARNING, INFO, DEBUG
# this level is used by the configured loggers (see "event-handlers") as soon # this level is used by the configured loggers (see "event-handlers") as soon
# as they have been started; before that, see "stdout-loglevel" # as they have been started; before that, see "stdout-loglevel"
stdout-loglevel = "INFO" # Loglevel for the very basic logger activated during AkkaApplication startup stdout-loglevel = "WARNING" # Loglevel for the very basic logger activated during AkkaApplication startup
# FIXME: Is there any sensible reason why we have 2 different log levels? # FIXME: Is there any sensible reason why we have 2 different log levels?
logConfigOnStart = off # Log the complete configuration at INFO level when the actor system is started. logConfigOnStart = off # Log the complete configuration at INFO level when the actor system is started.
@ -125,6 +125,26 @@ akka {
fsm = off # enable DEBUG logging of all LoggingFSMs for events, transitions and timers fsm = off # enable DEBUG logging of all LoggingFSMs for events, transitions and timers
event-stream = off # enable DEBUG logging of subscription changes on the eventStream event-stream = off # enable DEBUG logging of subscription changes on the eventStream
} }
# Entries for pluggable serializers and their bindings. If a binding for a specific class is not found,
# then the default serializer (Java serialization) is used.
#
serializers {
# java = "akka.serialization.JavaSerializer"
# proto = "akka.testing.ProtobufSerializer"
# sjson = "akka.testing.SJSONSerializer"
default = "akka.serialization.JavaSerializer"
}
# serialization-bindings {
# java = ["akka.serialization.SerializeSpec$Address",
# "akka.serialization.MyJavaSerializableActor",
# "akka.serialization.MyStatelessActorWithMessagesInMailbox",
# "akka.serialization.MyActorWithProtobufMessagesInMailbox"]
# sjson = ["akka.serialization.SerializeSpec$Person"]
# proto = ["com.google.protobuf.Message",
# "akka.actor.ProtobufProtocol$MyMessage"]
# }
} }
@ -142,6 +162,5 @@ akka {
tickDuration = 100ms tickDuration = 100ms
ticksPerWheel = 512 ticksPerWheel = 512
} }
} }

View file

@ -328,24 +328,24 @@ trait Actor {
* Puts the behavior on top of the hotswap stack. * Puts the behavior on top of the hotswap stack.
* If "discardOld" is true, an unbecome will be issued prior to pushing the new behavior to the stack * If "discardOld" is true, an unbecome will be issued prior to pushing the new behavior to the stack
*/ */
def become(behavior: Receive, discardOld: Boolean = true) { context.become(behavior, discardOld) } final def become(behavior: Receive, discardOld: Boolean = true) { context.become(behavior, discardOld) }
/** /**
* Reverts the Actor behavior to the previous one in the hotswap stack. * Reverts the Actor behavior to the previous one in the hotswap stack.
*/ */
def unbecome() { context.unbecome() } final def unbecome() { context.unbecome() }
/** /**
* Registers this actor as a Monitor for the provided ActorRef * Registers this actor as a Monitor for the provided ActorRef
* @return the provided ActorRef * @return the provided ActorRef
*/ */
def watch(subject: ActorRef): ActorRef = self startsWatching subject final def watch(subject: ActorRef): ActorRef = context startsWatching subject
/** /**
* Unregisters this actor as Monitor for the provided ActorRef * Unregisters this actor as Monitor for the provided ActorRef
* @return the provided ActorRef * @return the provided ActorRef
*/ */
def unwatch(subject: ActorRef): ActorRef = self stopsWatching subject final def unwatch(subject: ActorRef): ActorRef = context stopsWatching subject
// ========================================= // =========================================
// ==== INTERNAL IMPLEMENTATION DETAILS ==== // ==== INTERNAL IMPLEMENTATION DETAILS ====
@ -360,6 +360,6 @@ trait Actor {
} }
} }
private val processingBehavior = receive //ProcessingBehavior is the original behavior private[this] val processingBehavior = receive //ProcessingBehavior is the original behavior
} }

View file

@ -47,6 +47,10 @@ trait ActorContext extends ActorRefFactory {
def system: ActorSystem def system: ActorSystem
def parent: ActorRef def parent: ActorRef
def startsWatching(subject: ActorRef): ActorRef
def stopsWatching(subject: ActorRef): ActorRef
} }
private[akka] object ActorCell { private[akka] object ActorCell {
@ -136,13 +140,13 @@ private[akka] class ActorCell(
// ➡➡➡ NEVER SEND THE SAME SYSTEM MESSAGE OBJECT TO TWO ACTORS ⬅⬅⬅ // ➡➡➡ NEVER SEND THE SAME SYSTEM MESSAGE OBJECT TO TWO ACTORS ⬅⬅⬅
private[akka] def stop(): Unit = dispatcher.systemDispatch(this, Terminate()) private[akka] def stop(): Unit = dispatcher.systemDispatch(this, Terminate())
final def startsWatching(subject: ActorRef): ActorRef = { override final def startsWatching(subject: ActorRef): ActorRef = {
// ➡➡➡ NEVER SEND THE SAME SYSTEM MESSAGE OBJECT TO TWO ACTORS ⬅⬅⬅ // ➡➡➡ NEVER SEND THE SAME SYSTEM MESSAGE OBJECT TO TWO ACTORS ⬅⬅⬅
dispatcher.systemDispatch(this, Link(subject)) dispatcher.systemDispatch(this, Link(subject))
subject subject
} }
final def stopsWatching(subject: ActorRef): ActorRef = { override final def stopsWatching(subject: ActorRef): ActorRef = {
// ➡➡➡ NEVER SEND THE SAME SYSTEM MESSAGE OBJECT TO TWO ACTORS ⬅⬅⬅ // ➡➡➡ NEVER SEND THE SAME SYSTEM MESSAGE OBJECT TO TWO ACTORS ⬅⬅⬅
dispatcher.systemDispatch(this, Unlink(subject)) dispatcher.systemDispatch(this, Unlink(subject))
subject subject

View file

@ -46,7 +46,7 @@ import akka.event.DeathWatch
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a> * @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/ */
abstract class ActorRef extends java.lang.Comparable[ActorRef] with Serializable { abstract class ActorRef extends java.lang.Comparable[ActorRef] with Serializable {
scalaRef: ScalaActorRef scalaRef: ScalaActorRef with RefInternals
// Only mutable for RemoteServer in order to maintain identity across nodes // Only mutable for RemoteServer in order to maintain identity across nodes
/** /**
@ -108,16 +108,6 @@ abstract class ActorRef extends java.lang.Comparable[ActorRef] with Serializable
*/ */
def forward(message: Any)(implicit context: ActorContext) = tell(message, context.sender) def forward(message: Any)(implicit context: ActorContext) = tell(message, context.sender)
/**
* Suspends the actor. It will not process messages while suspended.
*/
def suspend(): Unit //TODO FIXME REMOVE THIS, ticket #1415
/**
* Resumes a suspended actor.
*/
def resume(): Unit //TODO FIXME REMOVE THIS, ticket #1415
/** /**
* Shuts down the actor its dispatcher and message queue. * Shuts down the actor its dispatcher and message queue.
*/ */
@ -128,24 +118,6 @@ abstract class ActorRef extends java.lang.Comparable[ActorRef] with Serializable
*/ */
def isTerminated: Boolean def isTerminated: Boolean
/**
* Registers this actor to be a death monitor of the provided ActorRef
* This means that this actor will get a Terminated()-message when the provided actor
* is permanently terminated.
*
* @return the same ActorRef that is provided to it, to allow for cleaner invocations
*/
def startsWatching(subject: ActorRef): ActorRef //TODO FIXME REMOVE THIS, ticket #1416
/**
* Deregisters this actor from being a death monitor of the provided ActorRef
* This means that this actor will not get a Terminated()-message when the provided actor
* is permanently terminated.
*
* @return the same ActorRef that is provided to it, to allow for cleaner invocations
*/
def stopsWatching(subject: ActorRef): ActorRef //TODO FIXME REMOVE THIS, ticket #1416
override def hashCode: Int = HashCode.hash(HashCode.SEED, address) override def hashCode: Int = HashCode.hash(HashCode.SEED, address)
override def equals(that: Any): Boolean = { override def equals(that: Any): Boolean = {
@ -169,7 +141,7 @@ class LocalActorRef private[akka] (
val systemService: Boolean = false, val systemService: Boolean = false,
_receiveTimeout: Option[Long] = None, _receiveTimeout: Option[Long] = None,
_hotswap: Stack[PartialFunction[Any, Unit]] = Props.noHotSwap) _hotswap: Stack[PartialFunction[Any, Unit]] = Props.noHotSwap)
extends ActorRef with ScalaActorRef { extends ActorRef with ScalaActorRef with RefInternals {
def name = path.name def name = path.name
@ -215,24 +187,6 @@ class LocalActorRef private[akka] (
*/ */
def stop(): Unit = actorCell.stop() def stop(): Unit = actorCell.stop()
/**
* Registers this actor to be a death monitor of the provided ActorRef
* This means that this actor will get a Terminated()-message when the provided actor
* is permanently terminated.
*
* @return the same ActorRef that is provided to it, to allow for cleaner invocations
*/
def startsWatching(subject: ActorRef): ActorRef = actorCell.startsWatching(subject)
/**
* Deregisters this actor from being a death monitor of the provided ActorRef
* This means that this actor will not get a Terminated()-message when the provided actor
* is permanently terminated.
*
* @return the same ActorRef that is provided to it, to allow for cleaner invocations
*/
def stopsWatching(subject: ActorRef): ActorRef = actorCell.stopsWatching(subject)
// ========= AKKA PROTECTED FUNCTIONS ========= // ========= AKKA PROTECTED FUNCTIONS =========
protected[akka] def underlying: ActorCell = actorCell protected[akka] def underlying: ActorCell = actorCell
@ -296,7 +250,11 @@ trait ScalaActorRef { ref: ActorRef ⇒
* implicit timeout * implicit timeout
*/ */
def ?(message: Any, timeout: Timeout)(implicit ignore: Int = 0): Future[Any] = ?(message)(timeout) def ?(message: Any, timeout: Timeout)(implicit ignore: Int = 0): Future[Any] = ?(message)(timeout)
}
private[akka] trait RefInternals {
def resume(): Unit
def suspend(): Unit
protected[akka] def restart(cause: Throwable): Unit protected[akka] def restart(cause: Throwable): Unit
} }
@ -324,15 +282,12 @@ case class SerializedActorRef(hostname: String, port: Int, path: String) {
/** /**
* Trait for ActorRef implementations where all methods contain default stubs. * Trait for ActorRef implementations where all methods contain default stubs.
*/ */
trait MinimalActorRef extends ActorRef with ScalaActorRef { trait MinimalActorRef extends ActorRef with ScalaActorRef with RefInternals {
private[akka] val uuid: Uuid = newUuid() private[akka] val uuid: Uuid = newUuid()
def name: String = uuid.toString def name: String = uuid.toString
//FIXME REMOVE THIS, ticket #1416 //FIXME REMOVE THIS, ticket #1416
def startsWatching(actorRef: ActorRef): ActorRef = actorRef
def stopsWatching(actorRef: ActorRef): ActorRef = actorRef
//FIXME REMOVE THIS, ticket #1415 //FIXME REMOVE THIS, ticket #1415
def suspend(): Unit = () def suspend(): Unit = ()
def resume(): Unit = () def resume(): Unit = ()

View file

@ -409,7 +409,12 @@ class LocalDeathWatch extends DeathWatch with ActorClassification {
} }
} }
class DefaultScheduler(hashedWheelTimer: HashedWheelTimer, system: ActorSystem) extends Scheduler { /**
* Scheduled tasks (Runnable and functions) are executed with the supplied dispatcher.
* Note that dispatcher is by-name parameter, because dispatcher might not be initialized
* when the scheduler is created.
*/
class DefaultScheduler(hashedWheelTimer: HashedWheelTimer, log: LoggingAdapter, dispatcher: MessageDispatcher) extends Scheduler {
def schedule(receiver: ActorRef, message: Any, initialDelay: Duration, delay: Duration): Cancellable = def schedule(receiver: ActorRef, message: Any, initialDelay: Duration, delay: Duration): Cancellable =
new DefaultCancellable(hashedWheelTimer.newTimeout(createContinuousTask(receiver, message, delay), initialDelay)) new DefaultCancellable(hashedWheelTimer.newTimeout(createContinuousTask(receiver, message, delay), initialDelay))
@ -429,8 +434,7 @@ class DefaultScheduler(hashedWheelTimer: HashedWheelTimer, system: ActorSystem)
private def createSingleTask(runnable: Runnable): TimerTask = private def createSingleTask(runnable: Runnable): TimerTask =
new TimerTask() { new TimerTask() {
def run(timeout: org.jboss.netty.akka.util.Timeout) { def run(timeout: org.jboss.netty.akka.util.Timeout) {
// FIXME: consider executing runnable inside main dispatcher to prevent blocking of scheduler dispatcher.dispatchTask(() runnable.run())
runnable.run()
} }
} }
@ -444,7 +448,7 @@ class DefaultScheduler(hashedWheelTimer: HashedWheelTimer, system: ActorSystem)
private def createSingleTask(f: () Unit): TimerTask = private def createSingleTask(f: () Unit): TimerTask =
new TimerTask { new TimerTask {
def run(timeout: org.jboss.netty.akka.util.Timeout) { def run(timeout: org.jboss.netty.akka.util.Timeout) {
f() dispatcher.dispatchTask(f)
} }
} }
@ -456,7 +460,7 @@ class DefaultScheduler(hashedWheelTimer: HashedWheelTimer, system: ActorSystem)
receiver ! message receiver ! message
timeout.getTimer.newTimeout(this, delay) timeout.getTimer.newTimeout(this, delay)
} else { } else {
system.eventStream.publish(Warning(this.getClass.getSimpleName, "Could not reschedule message to be sent because receiving actor has been terminated.")) log.warning("Could not reschedule message to be sent because receiving actor has been terminated.")
} }
} }
} }
@ -465,7 +469,7 @@ class DefaultScheduler(hashedWheelTimer: HashedWheelTimer, system: ActorSystem)
private def createContinuousTask(f: () Unit, delay: Duration): TimerTask = { private def createContinuousTask(f: () Unit, delay: Duration): TimerTask = {
new TimerTask { new TimerTask {
def run(timeout: org.jboss.netty.akka.util.Timeout) { def run(timeout: org.jboss.netty.akka.util.Timeout) {
f() dispatcher.dispatchTask(f)
timeout.getTimer.newTimeout(this, delay) timeout.getTimer.newTimeout(this, delay)
} }
} }

View file

@ -13,9 +13,10 @@ import java.util.concurrent.TimeUnit.MILLISECONDS
import java.util.concurrent.TimeUnit.NANOSECONDS import java.util.concurrent.TimeUnit.NANOSECONDS
import java.io.File import java.io.File
import com.typesafe.config.Config import com.typesafe.config.Config
import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.ConfigRoot
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.ConfigResolveOptions
import com.typesafe.config.ConfigException
import java.lang.reflect.InvocationTargetException import java.lang.reflect.InvocationTargetException
import akka.util.{ Helpers, Duration, ReflectiveAccess } import akka.util.{ Helpers, Duration, ReflectiveAccess }
import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.atomic.AtomicLong
@ -43,17 +44,32 @@ object ActorSystem {
def create(name: String, config: Config): ActorSystem = apply(name, config) def create(name: String, config: Config): ActorSystem = apply(name, config)
def apply(name: String, config: Config): ActorSystem = new ActorSystemImpl(name, config).start() def apply(name: String, config: Config): ActorSystem = new ActorSystemImpl(name, config).start()
/**
* Uses the standard default Config from ConfigFactory.load(), since none is provided.
*/
def create(name: String): ActorSystem = apply(name) def create(name: String): ActorSystem = apply(name)
def apply(name: String): ActorSystem = apply(name, DefaultConfigurationLoader.defaultConfig) /**
* Uses the standard default Config from ConfigFactory.load(), since none is provided.
*/
def apply(name: String): ActorSystem = apply(name, ConfigFactory.load())
def create(): ActorSystem = apply() def create(): ActorSystem = apply()
def apply(): ActorSystem = apply("default") def apply(): ActorSystem = apply("default")
class Settings(cfg: Config) { class Settings(cfg: Config) {
private def referenceConfig: Config =
ConfigFactory.parseResource(classOf[ActorSystem], "/akka-actor-reference.conf", // Verify that the Config is sane and has our reference config.
ConfigParseOptions.defaults.setAllowMissing(false)) val config: Config =
val config: ConfigRoot = ConfigFactory.emptyRoot("akka-actor").withFallback(cfg).withFallback(referenceConfig).resolve() try {
cfg.checkValid(ConfigFactory.defaultReference, "akka")
cfg
} catch {
case e: ConfigException
// try again with added defaultReference
val cfg2 = cfg.withFallback(ConfigFactory.defaultReference)
cfg2.checkValid(ConfigFactory.defaultReference, "akka")
cfg2
}
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
import config._ import config._
@ -102,9 +118,13 @@ object ActorSystem {
} }
object DefaultConfigurationLoader { // TODO move to migration kit
object OldConfigurationLoader {
val defaultConfig: Config = fromProperties orElse fromClasspath orElse fromHome getOrElse emptyConfig val defaultConfig: Config = {
val cfg = fromProperties orElse fromClasspath orElse fromHome getOrElse emptyConfig
cfg.withFallback(ConfigFactory.defaultReference).resolve(ConfigResolveOptions.defaults)
}
// file extensions (.conf, .json, .properties), are handled by parseFileAnySyntax // file extensions (.conf, .json, .properties), are handled by parseFileAnySyntax
val defaultLocation: String = (systemMode orElse envMode).map("akka." + _).getOrElse("akka") val defaultLocation: String = (systemMode orElse envMode).map("akka." + _).getOrElse("akka")
@ -130,7 +150,7 @@ object ActorSystem {
private def fromClasspath = try { private def fromClasspath = try {
Option(ConfigFactory.systemProperties.withFallback( Option(ConfigFactory.systemProperties.withFallback(
ConfigFactory.parseResourceAnySyntax(ActorSystem.getClass, "/" + defaultLocation, configParseOptions))) ConfigFactory.parseResourcesAnySyntax(ActorSystem.getClass, "/" + defaultLocation, configParseOptions)))
} catch { case _ None } } catch { case _ None }
private def fromHome = try { private def fromHome = try {
@ -233,7 +253,7 @@ abstract class ActorSystem extends ActorRefFactory {
* Register a block of code to run after all actors in this actor system have * Register a block of code to run after all actors in this actor system have
* been stopped. * been stopped.
*/ */
def registerOnTermination(code: Unit) def registerOnTermination[T](code: T)
/** /**
* Register a block of code to run after all actors in this actor system have * Register a block of code to run after all actors in this actor system have
@ -271,7 +291,7 @@ abstract class ActorSystem extends ActorRefFactory {
def hasExtension(ext: ExtensionId[_ <: Extension]): Boolean def hasExtension(ext: ExtensionId[_ <: Extension]): Boolean
} }
class ActorSystemImpl(val name: String, val applicationConfig: Config) extends ActorSystem { class ActorSystemImpl(val name: String, applicationConfig: Config) extends ActorSystem {
import ActorSystem._ import ActorSystem._
@ -290,7 +310,7 @@ class ActorSystemImpl(val name: String, val applicationConfig: Config) extends A
eventStream.startStdoutLogger(settings) eventStream.startStdoutLogger(settings)
val log = new BusLogging(eventStream, "ActorSystem") // this used only for .getClass in tagging messages val log = new BusLogging(eventStream, "ActorSystem") // this used only for .getClass in tagging messages
val scheduler = new DefaultScheduler(new HashedWheelTimer(log, Executors.defaultThreadFactory, settings.SchedulerTickDuration, settings.SchedulerTicksPerWheel), this) val scheduler = createScheduler()
val provider: ActorRefProvider = { val provider: ActorRefProvider = {
val providerClass = ReflectiveAccess.getClassFor(ProviderClass) match { val providerClass = ReflectiveAccess.getClassFor(ProviderClass) match {
@ -324,6 +344,7 @@ class ActorSystemImpl(val name: String, val applicationConfig: Config) extends A
} }
val dispatcherFactory = new Dispatchers(settings, DefaultDispatcherPrerequisites(eventStream, deadLetterMailbox, scheduler)) val dispatcherFactory = new Dispatchers(settings, DefaultDispatcherPrerequisites(eventStream, deadLetterMailbox, scheduler))
// TODO why implicit val dispatcher?
implicit val dispatcher = dispatcherFactory.defaultGlobalDispatcher implicit val dispatcher = dispatcherFactory.defaultGlobalDispatcher
//FIXME Set this to a Failure when things bubble to the top. What does this mean? //FIXME Set this to a Failure when things bubble to the top. What does this mean?
@ -352,16 +373,38 @@ class ActorSystemImpl(val name: String, val applicationConfig: Config) extends A
def start() = _start def start() = _start
def registerOnTermination(code: Unit) { terminationFuture onComplete (_ code) } def registerOnTermination[T](code: T) { terminationFuture onComplete (_ code) }
def registerOnTermination(code: Runnable) { terminationFuture onComplete (_ code.run) } def registerOnTermination(code: Runnable) { terminationFuture onComplete (_ code.run) }
// TODO shutdown all that other stuff, whatever that may be // TODO shutdown all that other stuff, whatever that may be
def stop() { def stop() {
guardian.stop() guardian.stop()
terminationFuture onComplete (_ scheduler.stop()) terminationFuture onComplete (_ stopScheduler())
terminationFuture onComplete (_ dispatcher.shutdown()) terminationFuture onComplete (_ dispatcher.shutdown())
} }
protected def createScheduler(): Scheduler = {
val threadFactory = new MonitorableThreadFactory("DefaultScheduler")
val hwt = new HashedWheelTimer(log, threadFactory, settings.SchedulerTickDuration, settings.SchedulerTicksPerWheel)
// note that dispatcher is by-name parameter in DefaultScheduler constructor,
// because dispatcher is not initialized when the scheduler is created
def safeDispatcher = {
if (dispatcher eq null) {
val exc = new IllegalStateException("Scheduler is using dispatcher before it has been initialized")
log.error(exc, exc.getMessage)
throw exc
} else {
dispatcher
}
}
new DefaultScheduler(hwt, log, safeDispatcher)
}
protected def stopScheduler(): Unit = scheduler match {
case x: DefaultScheduler x.stop()
case _
}
private val extensions = new ConcurrentIdentityHashMap[ExtensionId[_], AnyRef] private val extensions = new ConcurrentIdentityHashMap[ExtensionId[_], AnyRef]
/** /**

View file

@ -85,7 +85,7 @@ class Deployer(val settings: ActorSystem.Settings, val eventStream: EventStream,
} }
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
settings.config.getConfig("akka.actor.deployment").toObject.keySet.asScala settings.config.getConfig("akka.actor.deployment").root.keySet.asScala
.filterNot("default" ==) .filterNot("default" ==)
.map(path pathSubstring(path)) .map(path pathSubstring(path))
.toSet.toList // toSet to force uniqueness .toSet.toList // toSet to force uniqueness

View file

@ -122,12 +122,12 @@ abstract class FaultHandlingStrategy {
def handleSupervisorFailing(supervisor: ActorRef, children: Iterable[ActorRef]): Unit = { def handleSupervisorFailing(supervisor: ActorRef, children: Iterable[ActorRef]): Unit = {
if (children.nonEmpty) if (children.nonEmpty)
children.foreach(_.suspend()) children.foreach(_.asInstanceOf[RefInternals].suspend())
} }
def handleSupervisorRestarted(cause: Throwable, supervisor: ActorRef, children: Iterable[ActorRef]): Unit = { def handleSupervisorRestarted(cause: Throwable, supervisor: ActorRef, children: Iterable[ActorRef]): Unit = {
if (children.nonEmpty) if (children.nonEmpty)
children.foreach(_.restart(cause)) children.foreach(_.asInstanceOf[RefInternals].restart(cause))
} }
/** /**
@ -136,7 +136,7 @@ abstract class FaultHandlingStrategy {
def handleFailure(child: ActorRef, cause: Throwable, stats: ChildRestartStats, children: Iterable[ChildRestartStats]): Boolean = { def handleFailure(child: ActorRef, cause: Throwable, stats: ChildRestartStats, children: Iterable[ChildRestartStats]): Boolean = {
val action = if (decider.isDefinedAt(cause)) decider(cause) else Escalate val action = if (decider.isDefinedAt(cause)) decider(cause) else Escalate
action match { action match {
case Resume child.resume(); true case Resume child.asInstanceOf[RefInternals].resume(); true
case Restart processFailure(true, child, cause, stats, children); true case Restart processFailure(true, child, cause, stats, children); true
case Stop processFailure(false, child, cause, stats, children); true case Stop processFailure(false, child, cause, stats, children); true
case Escalate false case Escalate false
@ -194,7 +194,7 @@ case class AllForOneStrategy(decider: FaultHandlingStrategy.Decider,
def processFailure(restart: Boolean, child: ActorRef, cause: Throwable, stats: ChildRestartStats, children: Iterable[ChildRestartStats]): Unit = { def processFailure(restart: Boolean, child: ActorRef, cause: Throwable, stats: ChildRestartStats, children: Iterable[ChildRestartStats]): Unit = {
if (children.nonEmpty) { if (children.nonEmpty) {
if (restart && children.forall(_.requestRestartPermission(retriesWindow))) if (restart && children.forall(_.requestRestartPermission(retriesWindow)))
children.foreach(_.child.restart(cause)) children.foreach(_.child.asInstanceOf[RefInternals].restart(cause))
else else
children.foreach(_.child.stop()) children.foreach(_.child.stop())
} }
@ -247,7 +247,7 @@ case class OneForOneStrategy(decider: FaultHandlingStrategy.Decider,
def processFailure(restart: Boolean, child: ActorRef, cause: Throwable, stats: ChildRestartStats, children: Iterable[ChildRestartStats]): Unit = { def processFailure(restart: Boolean, child: ActorRef, cause: Throwable, stats: ChildRestartStats, children: Iterable[ChildRestartStats]): Unit = {
if (restart && stats.requestRestartPermission(retriesWindow)) if (restart && stats.requestRestartPermission(retriesWindow))
child.restart(cause) child.asInstanceOf[RefInternals].restart(cause)
else else
child.stop() //TODO optimization to drop child here already? child.stop() //TODO optimization to drop child here already?
} }

View file

@ -6,12 +6,9 @@ package akka.dispatch
import akka.event.Logging.Warning import akka.event.Logging.Warning
import java.util.concurrent.atomic.AtomicReference import java.util.concurrent.atomic.AtomicReference
import java.util.concurrent.{ TimeUnit, ExecutorService, RejectedExecutionException, ConcurrentLinkedQueue } import akka.actor.ActorCell
import akka.actor.{ ActorCell, ActorKilledException }
import akka.actor.ActorSystem
import akka.event.EventStream
import akka.actor.Scheduler
import akka.util.Duration import akka.util.Duration
import java.util.concurrent._
/** /**
* Default settings are: * Default settings are:
@ -156,4 +153,4 @@ abstract class PriorityGenerator extends java.util.Comparator[Envelope] {
final def compare(thisMessage: Envelope, thatMessage: Envelope): Int = final def compare(thisMessage: Envelope, thatMessage: Envelope): Int =
gen(thisMessage.message) - gen(thatMessage.message) gen(thisMessage.message) - gen(thatMessage.message)
} }

View file

@ -11,6 +11,9 @@ import akka.event.Logging.{ Warning, Error }
import akka.actor.ActorSystem import akka.actor.ActorSystem
import java.util.concurrent._ import java.util.concurrent._
import akka.event.EventStream import akka.event.EventStream
import concurrent.forkjoin.ForkJoinPool._
import concurrent.forkjoin.{ ForkJoinTask, ForkJoinWorkerThread, ForkJoinPool }
import concurrent.forkjoin.ForkJoinTask._
object ThreadPoolConfig { object ThreadPoolConfig {
type Bounds = Int type Bounds = Int
@ -184,6 +187,52 @@ class MonitorableThread(runnable: Runnable, name: String)
} }
} }
case class ForkJoinPoolConfig(targetParallelism: Int = Runtime.getRuntime.availableProcessors()) extends ExecutorServiceFactoryProvider {
final def createExecutorServiceFactory(name: String): ExecutorServiceFactory = new ExecutorServiceFactory {
def createExecutorService: ExecutorService = {
new ForkJoinPool(targetParallelism) with ExecutorService {
setAsyncMode(true)
setMaintainsParallelism(true)
override final def execute(r: Runnable) {
r match {
case fjmbox: FJMailbox
//fjmbox.fjTask.reinitialize()
Thread.currentThread match {
case fjwt: ForkJoinWorkerThread if fjwt.getPool eq this
fjmbox.fjTask.fork() //We should do fjwt.pushTask(fjmbox.fjTask) but it's package protected
case _ super.execute[Unit](fjmbox.fjTask)
}
case _
super.execute(r)
}
}
import java.util.{ Collection JCollection }
def invokeAny[T](callables: JCollection[_ <: Callable[T]]) =
throw new UnsupportedOperationException("invokeAny. NOT!")
def invokeAny[T](callables: JCollection[_ <: Callable[T]], l: Long, timeUnit: TimeUnit) =
throw new UnsupportedOperationException("invokeAny. NOT!")
def invokeAll[T](callables: JCollection[_ <: Callable[T]], l: Long, timeUnit: TimeUnit) =
throw new UnsupportedOperationException("invokeAny. NOT!")
}
}
}
}
trait FJMailbox { self: Mailbox
final val fjTask = new ForkJoinTask[Unit] with Runnable {
private[this] var result: Unit = ()
final def getRawResult() = result
final def setRawResult(v: Unit) { result = v }
final def exec() = { self.run(); true }
final def run() { invoke() }
}
}
/** /**
* As the name says * As the name says
*/ */

View file

@ -11,6 +11,7 @@ import scala.annotation.tailrec
import java.util.concurrent.atomic.{ AtomicReference, AtomicInteger } import java.util.concurrent.atomic.{ AtomicReference, AtomicInteger }
import java.net.InetSocketAddress import java.net.InetSocketAddress
import akka.remote.RemoteAddress import akka.remote.RemoteAddress
import collection.JavaConverters
/** /**
* An Iterable that also contains a version. * An Iterable that also contains a version.
@ -85,6 +86,10 @@ trait ConnectionManager {
*/ */
class LocalConnectionManager(initialConnections: Iterable[ActorRef]) extends ConnectionManager { class LocalConnectionManager(initialConnections: Iterable[ActorRef]) extends ConnectionManager {
def this(iterable: java.lang.Iterable[ActorRef]) {
this(JavaConverters.iterableAsScalaIterableConverter(iterable).asScala)
}
case class State(version: Long, connections: Iterable[ActorRef]) extends VersionedIterable[ActorRef] { case class State(version: Long, connections: Iterable[ActorRef]) extends VersionedIterable[ActorRef] {
def iterable = connections def iterable = connections
} }

View file

@ -9,11 +9,11 @@ import akka.actor._
import akka.config.ConfigurationException import akka.config.ConfigurationException
import akka.dispatch.{ Future, MessageDispatcher } import akka.dispatch.{ Future, MessageDispatcher }
import akka.util.{ ReflectiveAccess, Duration } import akka.util.{ ReflectiveAccess, Duration }
import java.net.InetSocketAddress
import java.lang.reflect.InvocationTargetException import java.lang.reflect.InvocationTargetException
import java.util.concurrent.atomic.{ AtomicReference, AtomicInteger } import java.util.concurrent.atomic.{ AtomicReference, AtomicInteger }
import scala.annotation.tailrec import scala.annotation.tailrec
import akka.japi.Creator
sealed trait RouterType sealed trait RouterType
@ -76,6 +76,12 @@ case class RoutedProps private[akka] (
connectionManager: ConnectionManager, connectionManager: ConnectionManager,
timeout: Timeout = RoutedProps.defaultTimeout, timeout: Timeout = RoutedProps.defaultTimeout,
localOnly: Boolean = RoutedProps.defaultLocalOnly) { localOnly: Boolean = RoutedProps.defaultLocalOnly) {
// Java API
def this(creator: Creator[Router], connectionManager: ConnectionManager, timeout: Timeout, localOnly: Boolean) {
this(() creator.create(), connectionManager, timeout, localOnly)
}
} }
object RoutedProps { object RoutedProps {

View file

@ -7,8 +7,7 @@ package akka.serialization
import akka.AkkaException import akka.AkkaException
import akka.util.ReflectiveAccess import akka.util.ReflectiveAccess
import scala.util.DynamicVariable import scala.util.DynamicVariable
import com.typesafe.config.{ ConfigRoot, ConfigParseOptions, ConfigFactory, Config } import com.typesafe.config.Config
import com.typesafe.config.Config._
import akka.config.ConfigurationException import akka.config.ConfigurationException
import akka.actor.{ Extension, ActorSystem, ActorSystemImpl } import akka.actor.{ Extension, ActorSystem, ActorSystemImpl }
@ -19,11 +18,7 @@ object Serialization {
// TODO ensure that these are always set (i.e. withValue()) when doing deserialization // TODO ensure that these are always set (i.e. withValue()) when doing deserialization
val currentSystem = new DynamicVariable[ActorSystemImpl](null) val currentSystem = new DynamicVariable[ActorSystemImpl](null)
class Settings(cfg: Config) { class Settings(val config: Config) {
private def referenceConfig: Config =
ConfigFactory.parseResource(classOf[ActorSystem], "/akka-serialization-reference.conf",
ConfigParseOptions.defaults.setAllowMissing(false))
val config: ConfigRoot = ConfigFactory.emptyRoot("akka-serialization").withFallback(cfg).withFallback(referenceConfig).resolve()
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
import config._ import config._
@ -37,7 +32,7 @@ object Serialization {
hasPath(configPath) match { hasPath(configPath) match {
case false Map() case false Map()
case true case true
val serializationBindings: Map[String, Seq[String]] = getConfig(configPath).toObject.unwrapped.asScala.toMap.map { val serializationBindings: Map[String, Seq[String]] = getConfig(configPath).root.unwrapped.asScala.toMap.map {
case (k: String, v: java.util.Collection[_]) (k -> v.asScala.toSeq.asInstanceOf[Seq[String]]) case (k: String, v: java.util.Collection[_]) (k -> v.asScala.toSeq.asInstanceOf[Seq[String]])
case invalid throw new ConfigurationException("Invalid serialization-bindings [%s]".format(invalid)) case invalid throw new ConfigurationException("Invalid serialization-bindings [%s]".format(invalid))
} }
@ -47,7 +42,7 @@ object Serialization {
} }
private def toStringMap(mapConfig: Config): Map[String, String] = private def toStringMap(mapConfig: Config): Map[String, String] =
mapConfig.toObject.unwrapped.asScala.toMap.map { case (k, v) (k, v.toString) } mapConfig.root.unwrapped.asScala.toMap.map { case (k, v) (k, v.toString) }
} }
} }
@ -58,7 +53,7 @@ object Serialization {
class Serialization(val system: ActorSystemImpl) extends Extension { class Serialization(val system: ActorSystemImpl) extends Extension {
import Serialization._ import Serialization._
val settings = new Settings(system.applicationConfig) val settings = new Settings(system.settings.config)
//TODO document me //TODO document me
def serialize(o: AnyRef): Either[Exception, Array[Byte]] = def serialize(o: AnyRef): Either[Exception, Array[Byte]] =

View file

@ -293,9 +293,6 @@ private[akka] class AsyncCallbackAdapter(exchange: Exchange, callback: AsyncCall
callback.done(false) callback.done(false)
} }
def startsWatching(actorRef: ActorRef): ActorRef = unsupported
def stopsWatching(actorRef: ActorRef): ActorRef = unsupported
def ?(message: Any)(implicit timeout: Timeout): Future[Any] = def ?(message: Any)(implicit timeout: Timeout): Future[Any] =
new KeptPromise[Any](Left(new UnsupportedOperationException("Ask/? is not supported for %s".format(getClass.getName)))) new KeptPromise[Any](Left(new UnsupportedOperationException("Ask/? is not supported for %s".format(getClass.getName))))
def restart(reason: Throwable): Unit = unsupported def restart(reason: Throwable): Unit = unsupported

View file

@ -6,7 +6,6 @@ import org.scalatest.matchers.MustMatchers
//#imports //#imports
import akka.actor.ActorSystem import akka.actor.ActorSystem
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
//#imports //#imports
@ -21,7 +20,7 @@ class ConfigDocSpec extends WordSpec {
nr-of-instances = 3 nr-of-instances = 3
} }
} }
""", ConfigParseOptions.defaults) """)
val system = ActorSystem("MySystem", ConfigFactory.systemProperties.withFallback(customConf)) val system = ActorSystem("MySystem", ConfigFactory.systemProperties.withFallback(customConf))
//#custom-config //#custom-config

View file

@ -16,6 +16,8 @@ configuration files that you see below. You can specify your own configuration f
property in the reference config. You only have to define the properties that differ from the default property in the reference config. You only have to define the properties that differ from the default
configuration. configuration.
FIXME: These default locations has changed
The location of the config file to use can be specified in various ways: The location of the config file to use can be specified in various ways:
* Define the ``-Dakka.config=...`` system property parameter with a file path to configuration file. * Define the ``-Dakka.config=...`` system property parameter with a file path to configuration file.
@ -44,47 +46,42 @@ Each Akka module has a reference configuration file with the default values.
*akka-actor:* *akka-actor:*
.. literalinclude:: ../../akka-actor/src/main/resources/akka-actor-reference.conf .. literalinclude:: ../../akka-actor/src/main/resources/reference.conf
:language: none :language: none
*akka-remote:* *akka-remote:*
.. literalinclude:: ../../akka-remote/src/main/resources/akka-remote-reference.conf .. literalinclude:: ../../akka-remote/src/main/resources/reference.conf
:language: none :language: none
*akka-serialization:*
.. literalinclude:: ../../akka-actor/src/main/resources/akka-serialization-reference.conf
:language: none
*akka-testkit:* *akka-testkit:*
.. literalinclude:: ../../akka-testkit/src/main/resources/akka-testkit-reference.conf .. literalinclude:: ../../akka-testkit/src/main/resources/reference.conf
:language: none :language: none
*akka-beanstalk-mailbox:* *akka-beanstalk-mailbox:*
.. literalinclude:: ../../akka-durable-mailboxes/akka-beanstalk-mailbox/src/main/resources/akka-beanstalk-mailbox-reference.conf .. literalinclude:: ../../akka-durable-mailboxes/akka-beanstalk-mailbox/src/main/resources/reference.conf
:language: none :language: none
*akka-file-mailbox:* *akka-file-mailbox:*
.. literalinclude:: ../../akka-durable-mailboxes/akka-file-mailbox/src/main/resources/akka-file-mailbox-reference.conf .. literalinclude:: ../../akka-durable-mailboxes/akka-file-mailbox/src/main/resources/reference.conf
:language: none :language: none
*akka-mongo-mailbox:* *akka-mongo-mailbox:*
.. literalinclude:: ../../akka-durable-mailboxes/akka-mongo-mailbox/src/main/resources/akka-mongo-mailbox-reference.conf .. literalinclude:: ../../akka-durable-mailboxes/akka-mongo-mailbox/src/main/resources/reference.conf
:language: none :language: none
*akka-redis-mailbox:* *akka-redis-mailbox:*
.. literalinclude:: ../../akka-durable-mailboxes/akka-redis-mailbox/src/main/resources/akka-redis-mailbox-reference.conf .. literalinclude:: ../../akka-durable-mailboxes/akka-redis-mailbox/src/main/resources/reference.conf
:language: none :language: none
*akka-zookeeper-mailbox:* *akka-zookeeper-mailbox:*
.. literalinclude:: ../../akka-durable-mailboxes/akka-zookeeper-mailbox/src/main/resources/akka-zookeeper-mailbox-reference.conf .. literalinclude:: ../../akka-durable-mailboxes/akka-zookeeper-mailbox/src/main/resources/reference.conf
:language: none :language: none
A custom ``akka.conf`` might look like this:: A custom ``akka.conf`` might look like this::
@ -121,7 +118,6 @@ A custom ``akka.conf`` might look like this::
} }
} }
.. _-Dakka.mode:
Config file format Config file format
------------------ ------------------
@ -129,9 +125,13 @@ Config file format
The configuration file syntax is described in the `HOCON <https://github.com/havocp/config/blob/master/HOCON.md>`_ The configuration file syntax is described in the `HOCON <https://github.com/havocp/config/blob/master/HOCON.md>`_
specification. Note that it supports three formats; conf, json, and properties. specification. Note that it supports three formats; conf, json, and properties.
.. _-Dakka.mode:
Specifying files for different modes Specifying files for different modes
------------------------------------ ------------------------------------
FIXME: mode doesn't exist, or will it?
You can use different configuration files for different purposes by specifying a mode option, either as You can use different configuration files for different purposes by specifying a mode option, either as
``-Dakka.mode=...`` system property or as ``AKKA_MODE=...`` environment variable. For example using DEBUG log level ``-Dakka.mode=...`` system property or as ``AKKA_MODE=...`` environment variable. For example using DEBUG log level
when in development mode. Run with ``-Dakka.mode=dev`` and place the following ``akka.dev.conf`` in the root of when in development mode. Run with ``-Dakka.mode=dev`` and place the following ``akka.dev.conf`` in the root of
@ -152,6 +152,8 @@ The mode option is not used when specifying the configuration file with ``-Dakka
Including files Including files
--------------- ---------------
FIXME: The include syntax has changed
Sometimes it can be useful to include another configuration file, for example if you have one ``akka.conf`` with all Sometimes it can be useful to include another configuration file, for example if you have one ``akka.conf`` with all
environment independent settings and then override some settings for specific modes. environment independent settings and then override some settings for specific modes.
@ -165,14 +167,14 @@ akka.dev.conf:
loglevel = "DEBUG" loglevel = "DEBUG"
} }
.. _-Dakka.output.config.source: .. _-Dakka.logConfigOnStart:
Showing Configuration Source Logging of Configuration
---------------------------- ------------------------
If the system property ``akka.output.config.source`` is set to anything but If the system or config property ``akka.logConfigOnStart`` is set to ``on``, then the
null, then the source from which Akka reads its configuration is printed to the complete configuration at INFO level when the actor system is started. This is useful
console during application startup. when you are uncertain of what configuration is used.
Summary of System Properties Summary of System Properties
---------------------------- ----------------------------
@ -180,4 +182,3 @@ Summary of System Properties
* :ref:`akka.home <-Dakka.home>` (``AKKA_HOME``): where Akka searches for configuration * :ref:`akka.home <-Dakka.home>` (``AKKA_HOME``): where Akka searches for configuration
* :ref:`akka.config <-Dakka.config>`: explicit configuration file location * :ref:`akka.config <-Dakka.config>`: explicit configuration file location
* :ref:`akka.mode <-Dakka.mode>` (``AKKA_MODE``): modify configuration file name for multiple profiles * :ref:`akka.mode <-Dakka.mode>` (``AKKA_MODE``): modify configuration file name for multiple profiles
* :ref:`akka.output.config.source <-Dakka.output.config.source>`: whether to print configuration source to console

View file

@ -155,6 +155,15 @@ Creating a Dispatcher with a priority mailbox using PriorityGenerator:
public class Main { public class Main {
// A simple Actor that just prints the messages it processes // A simple Actor that just prints the messages it processes
public static class MyActor extends UntypedActor { public static class MyActor extends UntypedActor {
public MyActor() {
self.tell("lowpriority");
getSelf().tell("lowpriority");
getSelf().tell("highpriority");
getSelf().tell("pigdog");
getSelf().tell("pigdog2");
getSelf().tell("pigdog3");
getSelf().tell("highpriority");
}
public void onReceive(Object message) throws Exception { public void onReceive(Object message) throws Exception {
System.out.println(message); System.out.println(message);
} }
@ -170,19 +179,9 @@ Creating a Dispatcher with a priority mailbox using PriorityGenerator:
} }
}; };
// We create an instance of the actor that will print out the messages it processes // We create an instance of the actor that will print out the messages it processes
ActorRef ref = Actors.actorOf(MyActor.class); // We create a new Priority dispatcher and seed it with the priority generator
// We create a new Priority dispatcher and seed it with the priority generator ActorRef ref = Actors.actorOf((new Props()).withCreator(MyActor.class).withDispatcher(new Dispatcher("foo", 5, new UnboundedPriorityMailbox(gen))));
ref.setDispatcher(new Dispatcher("foo", 5, new UnboundedPriorityMailbox(gen)));
ref.getDispatcher().suspend(ref); // Suspending the actor so it doesn't start to treat the messages before we have enqueued all of them :-)
ref.tell("lowpriority");
ref.tell("lowpriority");
ref.tell("highpriority");
ref.tell("pigdog");
ref.tell("pigdog2");
ref.tell("pigdog3");
ref.tell("highpriority");
ref.getDispatcher().resume(ref); // Resuming the actor so it will start treating its messages
} }
} }

View file

@ -178,10 +178,7 @@ The messages that it prevents are all that extends 'LifeCycleMessage':
* case object ReceiveTimeout * case object ReceiveTimeout
It also prevents the client from invoking any life-cycle and side-effecting methods, such as: It also prevents the client from invoking any life-cycle and side-effecting methods, such as:
* start
* stop * stop
* startsWatching
* stopsWatching
* etc. * etc.
Using secure cookie for remote client authentication Using secure cookie for remote client authentication

View file

@ -155,23 +155,18 @@ Creating a Dispatcher using PriorityGenerator:
val a = Actor.actorOf( // We create a new Actor that just prints out what it processes val a = Actor.actorOf( // We create a new Actor that just prints out what it processes
Props(new Actor { Props(new Actor {
self ! 'lowpriority
self ! 'lowpriority
self ! 'highpriority
self ! 'pigdog
self ! 'pigdog2
self ! 'pigdog3
self ! 'highpriority
def receive = { def receive = {
case x => println(x) case x => println(x)
} }
}).withDispatcher(new Dispatcher("foo", 5, UnboundedPriorityMailbox(gen)))) // We create a new Priority dispatcher and seed it with the priority generator }).withDispatcher(new Dispatcher("foo", 5, UnboundedPriorityMailbox(gen)))) // We create a new Priority dispatcher and seed it with the priority generator
a.dispatcher.suspend(a) // Suspending the actor so it doesn't start to treat the messages before we have enqueued all of them :-)
a ! 'lowpriority
a ! 'lowpriority
a ! 'highpriority
a ! 'pigdog
a ! 'pigdog2
a ! 'pigdog3
a ! 'highpriority
a.dispatcher.resume(a) // Resuming the actor so it will start treating its messages
Prints: Prints:
'highpriority 'highpriority

View file

@ -180,10 +180,7 @@ The messages that it prevents are all that extends 'LifeCycleMessage':
* class ReceiveTimeout..) * class ReceiveTimeout..)
It also prevents the client from invoking any life-cycle and side-effecting methods, such as: It also prevents the client from invoking any life-cycle and side-effecting methods, such as:
* start
* stop * stop
* startsWatching
* stopsWatching
* etc. * etc.
Using secure cookie for remote client authentication Using secure cookie for remote client authentication

View file

@ -4,23 +4,16 @@
package akka.actor.mailbox package akka.actor.mailbox
import com.typesafe.config.Config import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.ConfigRoot
import akka.util.Duration import akka.util.Duration
import java.util.concurrent.TimeUnit.MILLISECONDS import java.util.concurrent.TimeUnit.MILLISECONDS
import akka.actor._ import akka.actor._
object BeanstalkBasedMailboxExtension extends ExtensionId[BeanstalkMailboxSettings] with ExtensionIdProvider { object BeanstalkBasedMailboxExtension extends ExtensionId[BeanstalkMailboxSettings] with ExtensionIdProvider {
def lookup() = this def lookup() = this
def createExtension(system: ActorSystemImpl) = new BeanstalkMailboxSettings(system.applicationConfig) def createExtension(system: ActorSystemImpl) = new BeanstalkMailboxSettings(system.settings.config)
} }
class BeanstalkMailboxSettings(cfg: Config) extends Extension { class BeanstalkMailboxSettings(val config: Config) extends Extension {
private def referenceConfig: Config =
ConfigFactory.parseResource(classOf[ActorSystem], "/akka-beanstalk-mailbox-reference.conf",
ConfigParseOptions.defaults.setAllowMissing(false))
val config: ConfigRoot = ConfigFactory.emptyRoot("akka-beanstalk-mailbox").withFallback(cfg).withFallback(referenceConfig).resolve()
import config._ import config._

View file

@ -15,8 +15,8 @@ akka {
max-items = 2147483647 max-items = 2147483647
max-item-size = 2147483647 bytes max-item-size = 2147483647 bytes
max-age = 0s max-age = 0s
max-journal-size = 16 megabytes max-journal-size = 16 MiB
max-memory-size = 128 megabytes max-memory-size = 128 MiB
max-journal-overflow = 10 max-journal-overflow = 10
max-journal-size-absolute = 9223372036854775807 bytes max-journal-size-absolute = 9223372036854775807 bytes
discard-old-when-full = on discard-old-when-full = on

View file

@ -4,36 +4,29 @@
package akka.actor.mailbox package akka.actor.mailbox
import com.typesafe.config.Config import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.ConfigRoot
import akka.util.Duration import akka.util.Duration
import java.util.concurrent.TimeUnit.MILLISECONDS import java.util.concurrent.TimeUnit.MILLISECONDS
import akka.actor._ import akka.actor._
object FileBasedMailboxExtension extends ExtensionId[FileBasedMailboxSettings] with ExtensionIdProvider { object FileBasedMailboxExtension extends ExtensionId[FileBasedMailboxSettings] with ExtensionIdProvider {
def lookup() = this def lookup() = this
def createExtension(system: ActorSystemImpl) = new FileBasedMailboxSettings(system.applicationConfig) def createExtension(system: ActorSystemImpl) = new FileBasedMailboxSettings(system.settings.config)
} }
class FileBasedMailboxSettings(cfg: Config) extends Extension { class FileBasedMailboxSettings(val config: Config) extends Extension {
private def referenceConfig: Config =
ConfigFactory.parseResource(classOf[ActorSystem], "/akka-file-mailbox-reference.conf",
ConfigParseOptions.defaults.setAllowMissing(false))
val config: ConfigRoot = ConfigFactory.emptyRoot("akka-file-mailbox").withFallback(cfg).withFallback(referenceConfig).resolve()
import config._ import config._
val QueuePath = getString("akka.actor.mailbox.file-based.directory-path") val QueuePath = getString("akka.actor.mailbox.file-based.directory-path")
val MaxItems = getInt("akka.actor.mailbox.file-based.max-items") val MaxItems = getInt("akka.actor.mailbox.file-based.max-items")
val MaxSize = getMemorySizeInBytes("akka.actor.mailbox.file-based.max-size") val MaxSize = getBytes("akka.actor.mailbox.file-based.max-size")
val MaxItemSize = getMemorySizeInBytes("akka.actor.mailbox.file-based.max-item-size") val MaxItemSize = getBytes("akka.actor.mailbox.file-based.max-item-size")
val MaxAge = Duration(getMilliseconds("akka.actor.mailbox.file-based.max-age"), MILLISECONDS) val MaxAge = Duration(getMilliseconds("akka.actor.mailbox.file-based.max-age"), MILLISECONDS)
val MaxJournalSize = getMemorySizeInBytes("akka.actor.mailbox.file-based.max-journal-size") val MaxJournalSize = getBytes("akka.actor.mailbox.file-based.max-journal-size")
val MaxMemorySize = getMemorySizeInBytes("akka.actor.mailbox.file-based.max-memory-size") val MaxMemorySize = getBytes("akka.actor.mailbox.file-based.max-memory-size")
val MaxJournalOverflow = getInt("akka.actor.mailbox.file-based.max-journal-overflow") val MaxJournalOverflow = getInt("akka.actor.mailbox.file-based.max-journal-overflow")
val MaxJournalSizeAbsolute = getMemorySizeInBytes("akka.actor.mailbox.file-based.max-journal-size-absolute") val MaxJournalSizeAbsolute = getBytes("akka.actor.mailbox.file-based.max-journal-size-absolute")
val DiscardOldWhenFull = getBoolean("akka.actor.mailbox.file-based.discard-old-when-full") val DiscardOldWhenFull = getBoolean("akka.actor.mailbox.file-based.discard-old-when-full")
val KeepJournal = getBoolean("akka.actor.mailbox.file-based.keep-journal") val KeepJournal = getBoolean("akka.actor.mailbox.file-based.keep-journal")
val SyncJournal = getBoolean("akka.actor.mailbox.file-based.sync-journal") val SyncJournal = getBoolean("akka.actor.mailbox.file-based.sync-journal")

View file

@ -4,23 +4,16 @@
package akka.actor.mailbox package akka.actor.mailbox
import com.typesafe.config.Config import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.ConfigRoot
import akka.util.Duration import akka.util.Duration
import java.util.concurrent.TimeUnit.MILLISECONDS import java.util.concurrent.TimeUnit.MILLISECONDS
import akka.actor._ import akka.actor._
object MongoBasedMailboxExtension extends ExtensionId[MongoBasedMailboxSettings] with ExtensionIdProvider { object MongoBasedMailboxExtension extends ExtensionId[MongoBasedMailboxSettings] with ExtensionIdProvider {
def lookup() = this def lookup() = this
def createExtension(system: ActorSystemImpl) = new MongoBasedMailboxSettings(system.applicationConfig) def createExtension(system: ActorSystemImpl) = new MongoBasedMailboxSettings(system.settings.config)
} }
class MongoBasedMailboxSettings(cfg: Config) extends Extension { class MongoBasedMailboxSettings(val config: Config) extends Extension {
private def referenceConfig: Config =
ConfigFactory.parseResource(classOf[ActorSystem], "/akka-mongo-mailbox-reference.conf",
ConfigParseOptions.defaults.setAllowMissing(false))
val config: ConfigRoot = ConfigFactory.emptyRoot("akka-mongo-mailbox").withFallback(cfg).withFallback(referenceConfig).resolve()
import config._ import config._

View file

@ -4,21 +4,14 @@
package akka.actor.mailbox package akka.actor.mailbox
import com.typesafe.config.Config import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.ConfigRoot
import akka.actor._ import akka.actor._
object RedisBasedMailboxExtension extends ExtensionId[RedisBasedMailboxSettings] with ExtensionIdProvider { object RedisBasedMailboxExtension extends ExtensionId[RedisBasedMailboxSettings] with ExtensionIdProvider {
def lookup() = this def lookup() = this
def createExtension(system: ActorSystemImpl) = new RedisBasedMailboxSettings(system.applicationConfig) def createExtension(system: ActorSystemImpl) = new RedisBasedMailboxSettings(system.settings.config)
} }
class RedisBasedMailboxSettings(cfg: Config) extends Extension { class RedisBasedMailboxSettings(val config: Config) extends Extension {
private def referenceConfig: Config =
ConfigFactory.parseResource(classOf[ActorSystem], "/akka-redis-mailbox-reference.conf",
ConfigParseOptions.defaults.setAllowMissing(false))
val config: ConfigRoot = ConfigFactory.emptyRoot("akka-redis-mailbox").withFallback(cfg).withFallback(referenceConfig).resolve()
import config._ import config._

View file

@ -4,22 +4,15 @@
package akka.actor.mailbox package akka.actor.mailbox
import com.typesafe.config.Config import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.ConfigRoot
import akka.util.Duration import akka.util.Duration
import java.util.concurrent.TimeUnit.MILLISECONDS import java.util.concurrent.TimeUnit.MILLISECONDS
import akka.actor._ import akka.actor._
object ZooKeeperBasedMailboxExtension extends ExtensionId[ZooKeeperBasedMailboxSettings] with ExtensionIdProvider { object ZooKeeperBasedMailboxExtension extends ExtensionId[ZooKeeperBasedMailboxSettings] with ExtensionIdProvider {
def lookup() = this def lookup() = this
def createExtension(system: ActorSystemImpl) = new ZooKeeperBasedMailboxSettings(system.applicationConfig) def createExtension(system: ActorSystemImpl) = new ZooKeeperBasedMailboxSettings(system.settings.config)
} }
class ZooKeeperBasedMailboxSettings(cfg: Config) extends Extension { class ZooKeeperBasedMailboxSettings(val config: Config) extends Extension {
private def referenceConfig: Config =
ConfigFactory.parseResource(classOf[ActorSystem], "/akka-zookeeper-mailbox-reference.conf",
ConfigParseOptions.defaults.setAllowMissing(false))
val config: ConfigRoot = ConfigFactory.emptyRoot("akka-zookeeper-mailbox").withFallback(cfg).withFallback(referenceConfig).resolve()
import config._ import config._

View file

@ -39,7 +39,7 @@ akka {
server { server {
hostname = "" # The hostname or ip to bind the remoting to, InetAddress.getLocalHost.getHostAddress is used if empty hostname = "" # The hostname or ip to bind the remoting to, InetAddress.getLocalHost.getHostAddress is used if empty
port = 2552 # The default remote server port clients should connect to. Default is 2552 (AKKA) port = 2552 # The default remote server port clients should connect to. Default is 2552 (AKKA)
message-frame-size = 1048576 # Increase this if you want to be able to send messages with large payloads message-frame-size = 1 MiB # Increase this if you want to be able to send messages with large payloads
connection-timeout = 120s # Timeout duration connection-timeout = 120s # Timeout duration
require-cookie = off # Should the remote server require that it peers share the same secure-cookie (defined in the 'remote' section)? require-cookie = off # Should the remote server require that it peers share the same secure-cookie (defined in the 'remote' section)?
untrusted-mode = off # Enable untrusted mode for full security of server managed actors, allows untrusted clients to connect. untrusted-mode = off # Enable untrusted mode for full security of server managed actors, allows untrusted clients to connect.
@ -54,7 +54,7 @@ akka {
} }
reconnect-delay = 5s reconnect-delay = 5s
read-timeout = 3600s read-timeout = 3600s
message-frame-size = 1048576 message-frame-size = 1 MiB
reconnection-time-window = 600s # Maximum time window that a client should try to reconnect for reconnection-time-window = 600s # Maximum time window that a client should try to reconnect for
} }
} }

View file

@ -285,7 +285,7 @@ private[akka] case class RemoteActorRef private[akka] (
remoteAddress: RemoteAddress, remoteAddress: RemoteAddress,
path: ActorPath, path: ActorPath,
loader: Option[ClassLoader]) loader: Option[ClassLoader])
extends ActorRef with ScalaActorRef { extends ActorRef with ScalaActorRef with RefInternals {
@volatile @volatile
private var running: Boolean = true private var running: Boolean = true
@ -296,7 +296,7 @@ private[akka] case class RemoteActorRef private[akka] (
def isTerminated: Boolean = !running def isTerminated: Boolean = !running
protected[akka] def sendSystemMessage(message: SystemMessage): Unit = unsupported protected[akka] def sendSystemMessage(message: SystemMessage): Unit = throw new UnsupportedOperationException("Not supported for RemoteActorRef")
override def !(message: Any)(implicit sender: ActorRef = null): Unit = remote.send(message, Option(sender), remoteAddress, this, loader) override def !(message: Any)(implicit sender: ActorRef = null): Unit = remote.send(message, Option(sender), remoteAddress, this, loader)
@ -318,11 +318,5 @@ private[akka] case class RemoteActorRef private[akka] (
@throws(classOf[java.io.ObjectStreamException]) @throws(classOf[java.io.ObjectStreamException])
private def writeReplace(): AnyRef = provider.serialize(this) private def writeReplace(): AnyRef = provider.serialize(this)
def startsWatching(actorRef: ActorRef): ActorRef = unsupported ////FIXME Implement Remote DeathWatch, ticket #1190
def stopsWatching(actorRef: ActorRef): ActorRef = unsupported ////FIXME Implement Remote DeathWatch, ticket #1190
protected[akka] def restart(cause: Throwable): Unit = () protected[akka] def restart(cause: Throwable): Unit = ()
private def unsupported = throw new UnsupportedOperationException("Not supported for RemoteActorRef")
} }

View file

@ -4,9 +4,6 @@
package akka.remote package akka.remote
import com.typesafe.config.Config import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.ConfigRoot
import akka.util.Duration import akka.util.Duration
import java.util.concurrent.TimeUnit.MILLISECONDS import java.util.concurrent.TimeUnit.MILLISECONDS
import java.net.InetAddress import java.net.InetAddress
@ -17,14 +14,10 @@ import scala.collection.JavaConverters._
object RemoteExtension extends ExtensionId[RemoteExtensionSettings] with ExtensionIdProvider { object RemoteExtension extends ExtensionId[RemoteExtensionSettings] with ExtensionIdProvider {
def lookup() = this def lookup() = this
def createExtension(system: ActorSystemImpl) = new RemoteExtensionSettings(system.applicationConfig) def createExtension(system: ActorSystemImpl) = new RemoteExtensionSettings(system.settings.config)
} }
class RemoteExtensionSettings(cfg: Config) extends Extension { class RemoteExtensionSettings(val config: Config) extends Extension {
private def referenceConfig: Config =
ConfigFactory.parseResource(classOf[ActorSystem], "/akka-remote-reference.conf",
ConfigParseOptions.defaults.setAllowMissing(false))
val config: ConfigRoot = ConfigFactory.emptyRoot("akka-remote").withFallback(cfg).withFallback(referenceConfig).resolve()
import config._ import config._
@ -57,12 +50,12 @@ class RemoteExtensionSettings(cfg: Config) extends Extension {
val ReconnectionTimeWindow = Duration(config.getMilliseconds("akka.remote.client.reconnection-time-window"), MILLISECONDS) val ReconnectionTimeWindow = Duration(config.getMilliseconds("akka.remote.client.reconnection-time-window"), MILLISECONDS)
val ReadTimeout = Duration(config.getMilliseconds("akka.remote.client.read-timeout"), MILLISECONDS) val ReadTimeout = Duration(config.getMilliseconds("akka.remote.client.read-timeout"), MILLISECONDS)
val ReconnectDelay = Duration(config.getMilliseconds("akka.remote.client.reconnect-delay"), MILLISECONDS) val ReconnectDelay = Duration(config.getMilliseconds("akka.remote.client.reconnect-delay"), MILLISECONDS)
val MessageFrameSize = config.getInt("akka.remote.client.message-frame-size") val MessageFrameSize = config.getBytes("akka.remote.client.message-frame-size").toInt
} }
class RemoteServerSettings { class RemoteServerSettings {
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
val MessageFrameSize = config.getInt("akka.remote.server.message-frame-size") val MessageFrameSize = config.getBytes("akka.remote.server.message-frame-size").toInt
val SecureCookie: Option[String] = config.getString("akka.remote.secure-cookie") match { val SecureCookie: Option[String] = config.getString("akka.remote.secure-cookie") match {
case "" None case "" None
case cookie Some(cookie) case cookie Some(cookie)

View file

@ -59,35 +59,34 @@ abstract class RemoteClient private[akka] (
/** /**
* Converts the message to the wireprotocol and sends the message across the wire * Converts the message to the wireprotocol and sends the message across the wire
*/ */
def send(message: Any, senderOption: Option[ActorRef], recipient: ActorRef): Unit = def send(message: Any, senderOption: Option[ActorRef], recipient: ActorRef): Unit = if (isRunning) {
send(remoteSupport.createRemoteMessageProtocolBuilder(Left(recipient), Right(message), senderOption).build) send(remoteSupport.createRemoteMessageProtocolBuilder(Left(recipient), Right(message), senderOption).build)
} else {
val exception = new RemoteClientException("RemoteModule client is not running, make sure you have invoked 'RemoteClient.connect()' before using it.", remoteSupport, remoteAddress)
remoteSupport.notifyListeners(RemoteClientError(exception, remoteSupport, remoteAddress))
throw exception
}
/** /**
* Sends the message across the wire * Sends the message across the wire
*/ */
def send(request: RemoteMessageProtocol) { def send(request: RemoteMessageProtocol): Unit = {
if (isRunning) { //FIXME RACY, ticket #1409 log.debug("Sending message: {}", new RemoteMessage(request, remoteSupport))
log.debug("Sending message: " + new RemoteMessage(request, remoteSupport))
try { try {
val payload = remoteSupport.createMessageSendEnvelope(request) val payload = remoteSupport.createMessageSendEnvelope(request)
currentChannel.write(payload).addListener( currentChannel.write(payload).addListener(
new ChannelFutureListener { new ChannelFutureListener {
def operationComplete(future: ChannelFuture) { def operationComplete(future: ChannelFuture) {
if (future.isCancelled) { if (future.isCancelled) {
//Not interesting at the moment //Not interesting at the moment
} else if (!future.isSuccess) { } else if (!future.isSuccess) {
remoteSupport.notifyListeners(RemoteClientWriteFailed(payload, future.getCause, remoteSupport, remoteAddress)) remoteSupport.notifyListeners(RemoteClientWriteFailed(payload, future.getCause, remoteSupport, remoteAddress))
}
} }
}) }
} catch { })
case e: Exception remoteSupport.notifyListeners(RemoteClientError(e, remoteSupport, remoteAddress)) } catch {
} case e: Exception remoteSupport.notifyListeners(RemoteClientError(e, remoteSupport, remoteAddress))
} else {
val exception = new RemoteClientException("RemoteModule client is not running, make sure you have invoked 'RemoteClient.connect()' before using it.", remoteSupport, remoteAddress)
remoteSupport.notifyListeners(RemoteClientError(exception, remoteSupport, remoteAddress))
throw exception
} }
} }
@ -132,8 +131,7 @@ class ActiveRemoteClient private[akka] (
private[remote] var connection: ChannelFuture = _ private[remote] var connection: ChannelFuture = _
@volatile @volatile
private[remote] var openChannels: DefaultChannelGroup = _ private[remote] var openChannels: DefaultChannelGroup = _
@volatile
private var timer: HashedWheelTimer = _
@volatile @volatile
private var reconnectionTimeWindowStart = 0L private var reconnectionTimeWindowStart = 0L
@ -180,10 +178,9 @@ class ActiveRemoteClient private[akka] (
runSwitch switchOn { runSwitch switchOn {
openChannels = new DefaultDisposableChannelGroup(classOf[RemoteClient].getName) openChannels = new DefaultDisposableChannelGroup(classOf[RemoteClient].getName)
timer = new HashedWheelTimer
bootstrap = new ClientBootstrap(new NioClientSocketChannelFactory(Executors.newCachedThreadPool, Executors.newCachedThreadPool)) bootstrap = new ClientBootstrap(new NioClientSocketChannelFactory(Executors.newCachedThreadPool, Executors.newCachedThreadPool))
bootstrap.setPipelineFactory(new ActiveRemoteClientPipelineFactory(name, bootstrap, remoteAddress, timer, this)) bootstrap.setPipelineFactory(new ActiveRemoteClientPipelineFactory(name, bootstrap, remoteAddress, this))
bootstrap.setOption("tcpNoDelay", true) bootstrap.setOption("tcpNoDelay", true)
bootstrap.setOption("keepAlive", true) bootstrap.setOption("keepAlive", true)
@ -219,8 +216,6 @@ class ActiveRemoteClient private[akka] (
log.debug("Shutting down remote client [{}]", name) log.debug("Shutting down remote client [{}]", name)
notifyListeners(RemoteClientShutdown(remoteSupport, remoteAddress)) notifyListeners(RemoteClientShutdown(remoteSupport, remoteAddress))
timer.stop()
timer = null
openChannels.close.awaitUninterruptibly openChannels.close.awaitUninterruptibly
openChannels = null openChannels = null
bootstrap.releaseExternalResources() bootstrap.releaseExternalResources()
@ -253,18 +248,17 @@ class ActiveRemoteClientPipelineFactory(
name: String, name: String,
bootstrap: ClientBootstrap, bootstrap: ClientBootstrap,
remoteAddress: RemoteAddress, remoteAddress: RemoteAddress,
timer: HashedWheelTimer,
client: ActiveRemoteClient) extends ChannelPipelineFactory { client: ActiveRemoteClient) extends ChannelPipelineFactory {
import client.remoteSupport.clientSettings._ import client.remoteSupport.clientSettings._
def getPipeline: ChannelPipeline = { def getPipeline: ChannelPipeline = {
val timeout = new ReadTimeoutHandler(timer, ReadTimeout.length, ReadTimeout.unit) val timeout = new ReadTimeoutHandler(client.remoteSupport.timer, ReadTimeout.length, ReadTimeout.unit)
val lenDec = new LengthFieldBasedFrameDecoder(MessageFrameSize, 0, 4, 0, 4) val lenDec = new LengthFieldBasedFrameDecoder(MessageFrameSize, 0, 4, 0, 4)
val lenPrep = new LengthFieldPrepender(4) val lenPrep = new LengthFieldPrepender(4)
val protobufDec = new ProtobufDecoder(AkkaRemoteProtocol.getDefaultInstance) val protobufDec = new ProtobufDecoder(AkkaRemoteProtocol.getDefaultInstance)
val protobufEnc = new ProtobufEncoder val protobufEnc = new ProtobufEncoder
val remoteClient = new ActiveRemoteClientHandler(name, bootstrap, remoteAddress, timer, client) val remoteClient = new ActiveRemoteClientHandler(name, bootstrap, remoteAddress, client.remoteSupport.timer, client)
new StaticChannelPipeline(timeout, lenDec, protobufDec, lenPrep, protobufEnc, remoteClient) new StaticChannelPipeline(timeout, lenDec, protobufDec, lenPrep, protobufEnc, remoteClient)
} }
@ -361,6 +355,10 @@ class NettyRemoteSupport(_system: ActorSystem) extends RemoteSupport(_system) wi
val serverSettings = RemoteExtension(system).serverSettings val serverSettings = RemoteExtension(system).serverSettings
val clientSettings = RemoteExtension(system).clientSettings val clientSettings = RemoteExtension(system).clientSettings
val timer: HashedWheelTimer = new HashedWheelTimer
_system.registerOnTermination(timer.stop()) //Shut this guy down at the end
private val remoteClients = new HashMap[RemoteAddress, RemoteClient] private val remoteClients = new HashMap[RemoteAddress, RemoteClient]
private val clientsLock = new ReentrantReadWriteLock private val clientsLock = new ReentrantReadWriteLock
@ -519,6 +517,10 @@ class NettyRemoteServer(val remoteSupport: NettyRemoteSupport, val loader: Optio
try { try {
val shutdownSignal = { val shutdownSignal = {
val b = RemoteControlProtocol.newBuilder.setCommandType(CommandType.SHUTDOWN) val b = RemoteControlProtocol.newBuilder.setCommandType(CommandType.SHUTDOWN)
b.setOrigin(RemoteProtocol.AddressProtocol.newBuilder
.setHostname(address.hostname)
.setPort(address.port)
.build)
if (SecureCookie.nonEmpty) if (SecureCookie.nonEmpty)
b.setCookie(SecureCookie.get) b.setCookie(SecureCookie.get)
b.build b.build

View file

@ -6,8 +6,27 @@ package akka.remote
import akka.testkit._ import akka.testkit._
import akka.actor.ActorSystemImpl import akka.actor.ActorSystemImpl
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.ConfigResolveOptions
import java.io.File
abstract class AkkaRemoteSpec extends AkkaSpec with MultiJvmSync { object AkkaRemoteSpec {
private def configParseOptions = ConfigParseOptions.defaults.setAllowMissing(false)
val testConf: Config = {
System.getProperty("akka.config") match {
case null AkkaSpec.testConf
case location
ConfigFactory.systemProperties
.withFallback(ConfigFactory.parseFileAnySyntax(new File(location), configParseOptions))
.withFallback(ConfigFactory.defaultReference).resolve(ConfigResolveOptions.defaults)
}
}
}
abstract class AkkaRemoteSpec extends AkkaSpec(AkkaRemoteSpec.testConf) with MultiJvmSync {
/** /**
* Helper function for accessing the underlying remoting. * Helper function for accessing the underlying remoting.

View file

@ -20,7 +20,7 @@ trait NetworkFailureSpec { self: AkkaSpec ⇒
val BytesPerSecond = "60KByte/s" val BytesPerSecond = "60KByte/s"
val DelayMillis = "350ms" val DelayMillis = "350ms"
val PortRang = "1024-65535" val PortRange = "1024-65535"
def replyWithTcpResetFor(duration: Duration, dead: AtomicBoolean) = { def replyWithTcpResetFor(duration: Duration, dead: AtomicBoolean) = {
Future { Future {
@ -82,12 +82,12 @@ trait NetworkFailureSpec { self: AkkaSpec ⇒
def enableNetworkDrop() = { def enableNetworkDrop() = {
restoreIP() restoreIP()
assert(new ProcessBuilder("ipfw", "add", "1", "deny", "tcp", "from", "any", "to", "any", PortRang).start.waitFor == 0) assert(new ProcessBuilder("ipfw", "add", "1", "deny", "tcp", "from", "any", "to", "any", PortRange).start.waitFor == 0)
} }
def enableTcpReset() = { def enableTcpReset() = {
restoreIP() restoreIP()
assert(new ProcessBuilder("ipfw", "add", "1", "reset", "tcp", "from", "any", "to", "any", PortRang).start.waitFor == 0) assert(new ProcessBuilder("ipfw", "add", "1", "reset", "tcp", "from", "any", "to", "any", PortRange).start.waitFor == 0)
} }
def restoreIP() = { def restoreIP() = {

View file

@ -19,7 +19,7 @@ class RemoteConfigSpec extends AkkaSpec("akka.cluster.nodename = node1") {
//akka.remote.server //akka.remote.server
getInt("akka.remote.server.port") must equal(2552) getInt("akka.remote.server.port") must equal(2552)
getInt("akka.remote.server.message-frame-size") must equal(1048576) getBytes("akka.remote.server.message-frame-size") must equal(1048576L)
getMilliseconds("akka.remote.server.connection-timeout") must equal(120 * 1000) getMilliseconds("akka.remote.server.connection-timeout") must equal(120 * 1000)
getBoolean("akka.remote.server.require-cookie") must equal(false) getBoolean("akka.remote.server.require-cookie") must equal(false)
getBoolean("akka.remote.server.untrusted-mode") must equal(false) getBoolean("akka.remote.server.untrusted-mode") must equal(false)

View file

@ -0,0 +1,28 @@
FSM
===
Requirements
------------
To build and run FSM you need [Simple Build Tool][sbt] (sbt).
Running
-------
First time, 'sbt update' to get dependencies, then to run Ants use 'sbt run'.
Here is an example. First type 'sbt' to start SBT interactively, the run 'update' and 'run':
> cd $AKKA_HOME
> % sbt
> > project akka-sample-fsm
> > run
> > Choose 1 or 2 depending on what sample you wish to run
Notice
------
[akka]: http://akka.io
[sbt]: http://code.google.com/p/simple-build-tool/

View file

@ -1,3 +1,6 @@
/**
* Copyright (C) 2009-2010 Typesafe Inc. <http://www.typesafe.com>.
*/
package sample.fsm.buncher package sample.fsm.buncher
import akka.actor.ActorRefFactory import akka.actor.ActorRefFactory
@ -6,15 +9,15 @@ import akka.util.Duration
import akka.actor.{ FSM, Actor, ActorRef } import akka.actor.{ FSM, Actor, ActorRef }
/* /*
* generic typed object buncher. * generic typed object buncher.
* *
* To instantiate it, use the factory method like so: * To instantiate it, use the factory method like so:
* Buncher(100, 500)(x : List[AnyRef] => x foreach println) * Buncher(100, 500)(x : List[AnyRef] => x foreach println)
* which will yield a fully functional ActorRef. * which will yield a fully functional ActorRef.
* The type of messages allowed is strongly typed to match the * The type of messages allowed is strongly typed to match the
* supplied processing method; other messages are discarded (and * supplied processing method; other messages are discarded (and
* possibly logged). * possibly logged).
*/ */
object GenericBuncher { object GenericBuncher {
trait State trait State
case object Idle extends State case object Idle extends State

View file

@ -1,3 +1,6 @@
/**
* Copyright (C) 2009-2010 Typesafe Inc. <http://www.typesafe.com>.
*/
package sample.fsm.dining.become package sample.fsm.dining.become
//Akka adaptation of //Akka adaptation of
@ -7,8 +10,8 @@ import akka.actor.{ ActorRef, Actor, ActorSystem }
import akka.util.duration._ import akka.util.duration._
/* /*
* First we define our messages, they basically speak for themselves * First we define our messages, they basically speak for themselves
*/ */
sealed trait DiningHakkerMessage sealed trait DiningHakkerMessage
case class Busy(chopstick: ActorRef) extends DiningHakkerMessage case class Busy(chopstick: ActorRef) extends DiningHakkerMessage
case class Put(hakker: ActorRef) extends DiningHakkerMessage case class Put(hakker: ActorRef) extends DiningHakkerMessage
@ -18,9 +21,9 @@ object Eat extends DiningHakkerMessage
object Think extends DiningHakkerMessage object Think extends DiningHakkerMessage
/* /*
* A Chopstick is an actor, it can be taken, and put back * A Chopstick is an actor, it can be taken, and put back
*/ */
class Chopstick(name: String) extends Actor { class Chopstick extends Actor {
//When a Chopstick is taken by a hakker //When a Chopstick is taken by a hakker
//It will refuse to be taken by other hakkers //It will refuse to be taken by other hakkers
@ -44,8 +47,8 @@ class Chopstick(name: String) extends Actor {
} }
/* /*
* A hakker is an awesome dude or dudett who either thinks about hacking or has to eat ;-) * A hakker is an awesome dude or dudett who either thinks about hacking or has to eat ;-)
*/ */
class Hakker(name: String, left: ActorRef, right: ActorRef) extends Actor { class Hakker(name: String, left: ActorRef, right: ActorRef) extends Actor {
//When a hakker is thinking it can become hungry //When a hakker is thinking it can become hungry
@ -75,7 +78,7 @@ class Hakker(name: String, left: ActorRef, right: ActorRef) extends Actor {
//back to think about how he should obtain his chopsticks :-) //back to think about how he should obtain his chopsticks :-)
def waiting_for(chopstickToWaitFor: ActorRef, otherChopstick: ActorRef): Receive = { def waiting_for(chopstickToWaitFor: ActorRef, otherChopstick: ActorRef): Receive = {
case Taken(`chopstickToWaitFor`) case Taken(`chopstickToWaitFor`)
println("%s has picked up %s and %s, and starts to eat", name, left.address, right.address) println("%s has picked up %s and %s and starts to eat".format(name, left.name, right.name))
become(eating) become(eating)
system.scheduler.scheduleOnce(self, Think, 5 seconds) system.scheduler.scheduleOnce(self, Think, 5 seconds)
@ -105,27 +108,33 @@ class Hakker(name: String, left: ActorRef, right: ActorRef) extends Actor {
become(thinking) become(thinking)
left ! Put(self) left ! Put(self)
right ! Put(self) right ! Put(self)
println("%s puts down his chopsticks and starts to think", name) println("%s puts down his chopsticks and starts to think".format(name))
system.scheduler.scheduleOnce(self, Eat, 5 seconds) system.scheduler.scheduleOnce(self, Eat, 5 seconds)
} }
//All hakkers start in a non-eating state //All hakkers start in a non-eating state
def receive = { def receive = {
case Think case Think
println("%s starts to think", name) println("%s starts to think".format(name))
become(thinking) become(thinking)
system.scheduler.scheduleOnce(self, Eat, 5 seconds) system.scheduler.scheduleOnce(self, Eat, 5 seconds)
} }
} }
/* /*
* Alright, here's our test-harness * Alright, here's our test-harness
*/ */
object DiningHakkers { object DiningHakkers {
val system = ActorSystem() val system = ActorSystem()
def main(args: Array[String]): Unit = {
run
}
def run { def run {
//Create 5 chopsticks //Create 5 chopsticks
val chopsticks = for (i 1 to 5) yield system.actorOf(new Chopstick("Chopstick " + i)) val chopsticks = for (i 1 to 5) yield system.actorOf[Chopstick]("Chopstick " + i)
//Create 5 awesome hakkers and assign them their left and right chopstick //Create 5 awesome hakkers and assign them their left and right chopstick
val hakkers = for { val hakkers = for {
(name, i) List("Ghosh", "Bonér", "Klang", "Krasser", "Manie").zipWithIndex (name, i) List("Ghosh", "Bonér", "Klang", "Krasser", "Manie").zipWithIndex

View file

@ -1,3 +1,6 @@
/**
* Copyright (C) 2009-2010 Typesafe Inc. <http://www.typesafe.com>.
*/
package sample.fsm.dining.fsm package sample.fsm.dining.fsm
import akka.actor.{ ActorRef, Actor, FSM, ActorSystem } import akka.actor.{ ActorRef, Actor, FSM, ActorSystem }
@ -6,8 +9,8 @@ import akka.util.Duration
import akka.util.duration._ import akka.util.duration._
/* /*
* Some messages for the chopstick * Some messages for the chopstick
*/ */
sealed trait ChopstickMessage sealed trait ChopstickMessage
object Take extends ChopstickMessage object Take extends ChopstickMessage
object Put extends ChopstickMessage object Put extends ChopstickMessage
@ -27,9 +30,9 @@ case object Taken extends ChopstickState
case class TakenBy(hakker: ActorRef) case class TakenBy(hakker: ActorRef)
/* /*
* A chopstick is an actor, it can be taken, and put back * A chopstick is an actor, it can be taken, and put back
*/ */
class Chopstick(name: String) extends Actor with FSM[ChopstickState, TakenBy] { class Chopstick extends Actor with FSM[ChopstickState, TakenBy] {
// A chopstick begins its existence as available and taken by no one // A chopstick begins its existence as available and taken by no one
startWith(Available, TakenBy(system.deadLetters)) startWith(Available, TakenBy(system.deadLetters))
@ -77,8 +80,8 @@ case object Eating extends FSMHakkerState
case class TakenChopsticks(left: Option[ActorRef], right: Option[ActorRef]) case class TakenChopsticks(left: Option[ActorRef], right: Option[ActorRef])
/* /*
* A fsm hakker is an awesome dude or dudette who either thinks about hacking or has to eat ;-) * A fsm hakker is an awesome dude or dudette who either thinks about hacking or has to eat ;-)
*/ */
class FSMHakker(name: String, left: ActorRef, right: ActorRef) extends Actor with FSM[FSMHakkerState, TakenChopsticks] { class FSMHakker(name: String, left: ActorRef, right: ActorRef) extends Actor with FSM[FSMHakkerState, TakenChopsticks] {
//All hakkers start waiting //All hakkers start waiting
@ -86,7 +89,7 @@ class FSMHakker(name: String, left: ActorRef, right: ActorRef) extends Actor wit
when(Waiting) { when(Waiting) {
case Event(Think, _) case Event(Think, _)
println("%s starts to think", name) println("%s starts to think".format(name))
startThinking(5 seconds) startThinking(5 seconds)
} }
@ -125,7 +128,7 @@ class FSMHakker(name: String, left: ActorRef, right: ActorRef) extends Actor wit
} }
private def startEating(left: ActorRef, right: ActorRef): State = { private def startEating(left: ActorRef, right: ActorRef): State = {
println("%s has picked up %s and %s, and starts to eat", name, left.address, right.address) println("%s has picked up %s and %s and starts to eat".format(name, left.name, right.name))
goto(Eating) using TakenChopsticks(Some(left), Some(right)) forMax (5 seconds) goto(Eating) using TakenChopsticks(Some(left), Some(right)) forMax (5 seconds)
} }
@ -144,7 +147,7 @@ class FSMHakker(name: String, left: ActorRef, right: ActorRef) extends Actor wit
// then he puts down his chopsticks and starts to think // then he puts down his chopsticks and starts to think
when(Eating) { when(Eating) {
case Event(StateTimeout, _) case Event(StateTimeout, _)
println("%s puts down his chopsticks and starts to think", name) println("%s puts down his chopsticks and starts to think".format(name))
left ! Put left ! Put
right ! Put right ! Put
startThinking(5 seconds) startThinking(5 seconds)
@ -159,15 +162,19 @@ class FSMHakker(name: String, left: ActorRef, right: ActorRef) extends Actor wit
} }
/* /*
* Alright, here's our test-harness * Alright, here's our test-harness
*/ */
object DiningHakkersOnFsm { object DiningHakkersOnFsm {
val system = ActorSystem() val system = ActorSystem()
def main(args: Array[String]): Unit = {
run
}
def run = { def run = {
// Create 5 chopsticks // Create 5 chopsticks
val chopsticks = for (i 1 to 5) yield system.actorOf(new Chopstick("Chopstick " + i)) val chopsticks = for (i 1 to 5) yield system.actorOf[Chopstick]("Chopstick " + i)
// Create 5 awesome fsm hakkers and assign them their left and right chopstick // Create 5 awesome fsm hakkers and assign them their left and right chopstick
val hakkers = for { val hakkers = for {
(name, i) List("Ghosh", "Bonér", "Klang", "Krasser", "Manie").zipWithIndex (name, i) List("Ghosh", "Bonér", "Klang", "Krasser", "Manie").zipWithIndex

View file

@ -0,0 +1,26 @@
HELLO
=====
Requirements
------------
To build and run FSM you need [Simple Build Tool][sbt] (sbt).
Running
-------
First time, 'sbt update' to get dependencies, then to run Ants use 'sbt run'.
Here is an example. First type 'sbt' to start SBT interactively, the run 'update' and 'run':
> cd $AKKA_HOME
> % sbt
> > project akka-sample-hello
> > run
Notice
------
[akka]: http://akka.io
[sbt]: http://code.google.com/p/simple-build-tool/

View file

@ -0,0 +1,32 @@
/**
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
*/
package sample.hello
import akka.actor.{ ActorSystem, Actor }
case object Start
object Main {
def main(args: Array[String]): Unit = {
val system = ActorSystem()
system.actorOf[HelloActor] ! Start
}
}
class HelloActor extends Actor {
val worldActor = system.actorOf[WorldActor]
def receive = {
case Start worldActor ! "Hello"
case s: String
println("Received message: %s".format(s))
system.stop()
}
}
class WorldActor extends Actor {
def receive = {
case s: String sender ! s.toUpperCase + " world!"
}
}

View file

@ -10,11 +10,10 @@ import org.scalatest.junit.JUnitRunner
import org.scalatest.matchers.MustMatchers import org.scalatest.matchers.MustMatchers
import akka.actor.ActorSystem import akka.actor.ActorSystem
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import akka.testkit.AkkaSpec import akka.testkit.AkkaSpec
@RunWith(classOf[JUnitRunner]) @RunWith(classOf[JUnitRunner])
class ConfigSpec extends AkkaSpec(ConfigFactory.parseResource(classOf[ConfigSpec], "/akka-stm-reference.conf", ConfigParseOptions.defaults)) { class ConfigSpec extends AkkaSpec(ConfigFactory.defaultReference) {
"The default configuration file (i.e. akka-stm-reference.conf)" should { "The default configuration file (i.e. akka-stm-reference.conf)" should {
"contain all configuration properties for akka-stm that are used in code with their correct defaults" in { "contain all configuration properties for akka-stm that are used in code with their correct defaults" in {

View file

@ -42,6 +42,24 @@ class TestActorRef[T <: Actor](
*/ */
def underlyingActor: T = underlyingActorInstance.asInstanceOf[T] def underlyingActor: T = underlyingActorInstance.asInstanceOf[T]
/**
* Registers this actor to be a death monitor of the provided ActorRef
* This means that this actor will get a Terminated()-message when the provided actor
* is permanently terminated.
*
* @return the same ActorRef that is provided to it, to allow for cleaner invocations
*/
def startsWatching(subject: ActorRef): ActorRef = underlying.startsWatching(subject)
/**
* Deregisters this actor from being a death monitor of the provided ActorRef
* This means that this actor will not get a Terminated()-message when the provided actor
* is permanently terminated.
*
* @return the same ActorRef that is provided to it, to allow for cleaner invocations
*/
def stopsWatching(subject: ActorRef): ActorRef = underlying.stopsWatching(subject)
override def toString = "TestActor[" + address + "]" override def toString = "TestActor[" + address + "]"
override def equals(other: Any) = other.isInstanceOf[TestActorRef[_]] && other.asInstanceOf[TestActorRef[_]].address == address override def equals(other: Any) = other.isInstanceOf[TestActorRef[_]] && other.asInstanceOf[TestActorRef[_]].address == address

Some files were not shown because too many files have changed in this diff Show more