Merge branch 'master' into wip-ActorPath-rk

This commit is contained in:
Roland 2011-12-02 13:31:48 +01:00
commit e38cd19af9
152 changed files with 3741 additions and 1749 deletions

View file

@ -2,21 +2,36 @@ package akka.actor;
import akka.actor.ActorSystem;
import akka.japi.Creator;
import akka.testkit.AkkaSpec;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;
public class JavaAPI {
private ActorSystem system = ActorSystem.create();
private static ActorSystem system;
@BeforeClass
public static void beforeAll() {
system = ActorSystem.create("JavaAPI", AkkaSpec.testConf());
}
@AfterClass
public static void afterAll() {
system.stop();
system = null;
}
@Test
void mustBeAbleToCreateActorRefFromClass() {
public void mustBeAbleToCreateActorRefFromClass() {
ActorRef ref = system.actorOf(JavaAPITestActor.class);
assertNotNull(ref);
}
@Test
void mustBeAbleToCreateActorRefFromFactory() {
public void mustBeAbleToCreateActorRefFromFactory() {
ActorRef ref = system.actorOf(new Props().withCreator(new Creator<Actor>() {
public Actor create() {
return new JavaAPITestActor();
@ -26,7 +41,7 @@ public class JavaAPI {
}
@Test
void mustAcceptSingleArgTell() {
public void mustAcceptSingleArgTell() {
ActorRef ref = system.actorOf(JavaAPITestActor.class);
ref.tell("hallo");
ref.tell("hallo", ref);

View file

@ -3,18 +3,23 @@
*/
package akka.actor;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import akka.testkit.AkkaSpec;
import com.typesafe.config.ConfigFactory;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigParseOptions;
import static org.junit.Assert.*;
public class JavaExtension {
static class Provider implements ExtensionIdProvider {
public ExtensionId<TestExtension> lookup() { return defaultInstance; }
public ExtensionId<TestExtension> lookup() {
return defaultInstance;
}
}
public final static TestExtensionId defaultInstance = new TestExtensionId();
@ -26,10 +31,11 @@ public class JavaExtension {
}
static class TestExtension implements Extension {
public final ActorSystemImpl system;
public TestExtension(ActorSystemImpl i) {
system = i;
}
public final ActorSystemImpl system;
public TestExtension(ActorSystemImpl i) {
system = i;
}
}
static class OtherExtension implements Extension {
@ -41,10 +47,20 @@ public class JavaExtension {
}
}
private Config c = ConfigFactory.parseString("akka.extensions = [ \"akka.actor.JavaExtension$Provider\" ]",
ConfigParseOptions.defaults());
private static ActorSystem system;
private ActorSystem system = ActorSystem.create("JavaExtension", c);
@BeforeClass
public static void beforeAll() {
Config c = ConfigFactory.parseString("akka.extensions = [ \"akka.actor.JavaExtension$Provider\" ]").withFallback(
AkkaSpec.testConf());
system = ActorSystem.create("JavaExtension", c);
}
@AfterClass
public static void afterAll() {
system.stop();
system = null;
}
@Test
public void mustBeAccessible() {

View file

@ -2,6 +2,9 @@ package akka.dispatch;
import akka.actor.Timeout;
import akka.actor.ActorSystem;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;
import java.util.concurrent.Callable;
@ -14,15 +17,30 @@ import akka.japi.Function;
import akka.japi.Function2;
import akka.japi.Procedure;
import akka.japi.Option;
import akka.testkit.AkkaSpec;
public class JavaFutureTests {
private final ActorSystem system = ActorSystem.create();
private final Timeout t = system.settings().ActorTimeout();
private final FutureFactory ff = new FutureFactory(system.dispatcher(), t);
private static ActorSystem system;
private static FutureFactory ff;
private static Timeout t;
@BeforeClass
public static void beforeAll() {
system = ActorSystem.create("JavaFutureTests", AkkaSpec.testConf());
t = system.settings().ActorTimeout();
ff = new FutureFactory(system.dispatcher(), t);
}
@AfterClass
public static void afterAll() {
system.stop();
system = null;
}
@Test
public void mustBeAbleToMapAFuture() {
Future<String> f1 = ff.future(new Callable<String>() {
public String call() {
return "Hello";

View file

@ -7,7 +7,8 @@ import org.junit.Test;
public class JavaDuration {
@Test void testCreation() {
@Test
public void testCreation() {
final Duration fivesec = Duration.create(5, "seconds");
final Duration threemillis = Duration.parse("3 millis");
final Duration diff = fivesec.minus(threemillis);

View file

@ -9,17 +9,15 @@ import com.typesafe.config.ConfigFactory
class JavaExtensionSpec extends JavaExtension with JUnitSuite
object ActorSystemSpec {
object TestExtension extends ExtensionId[TestExtension] with ExtensionIdProvider {
def lookup = this
def createExtension(s: ActorSystemImpl) = new TestExtension(s)
}
class TestExtension(val system: ActorSystemImpl) extends Extension
object TestExtension extends ExtensionId[TestExtension] with ExtensionIdProvider {
def lookup = this
def createExtension(s: ActorSystemImpl) = new TestExtension(s)
}
class ActorSystemSpec extends AkkaSpec("""akka.extensions = ["akka.actor.ActorSystemSpec$TestExtension$"]""") {
import ActorSystemSpec._
// Dont't place inside ActorSystemSpec object, since it will not be garbage collected and reference to system remains
class TestExtension(val system: ActorSystemImpl) extends Extension
class ActorSystemSpec extends AkkaSpec("""akka.extensions = ["akka.actor.TestExtension$"]""") {
"An ActorSystem" must {

View file

@ -107,9 +107,10 @@ object Chameneos {
def run {
// System.setProperty("akka.config", "akka.conf")
Chameneos.start = System.currentTimeMillis
ActorSystem().actorOf(new Mall(1000000, 4))
val system = ActorSystem().actorOf(new Mall(1000000, 4))
Thread.sleep(10000)
println("Elapsed: " + (end - start))
system.stop()
}
def main(args: Array[String]): Unit = run

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])
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 {
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 {
val terminal = actorOf(Props(context { case _ }))
testActor startsWatching terminal
startWatching(terminal)
testActor ! "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 {
val terminal = actorOf(Props(context { case _ }))
val monitor1, monitor2, monitor3 =
actorOf(Props(new Actor {
watch(terminal)
def receive = { case t: Terminated testActor ! t }
}))
val monitor1, monitor2, monitor3 = startWatching(terminal)
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 {
val terminal = actorOf(Props(context { case _ }))
val monitor1, monitor3 =
actorOf(Props(new Actor {
watch(terminal)
def receive = { case t: Terminated testActor ! t }
}))
val monitor1, monitor3 = startWatching(terminal)
val monitor2 = actorOf(Props(new Actor {
watch(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 terminal = (supervisor ? terminalProps).as[ActorRef].get
val monitor = actorOf(Props(new Actor {
watch(terminal)
def receive = { case t: Terminated testActor ! t }
}))
val monitor = startWatching(terminal)
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
brother startsWatching failed
testActor startsWatching brother
val failed = (supervisor ? Props.empty).as[ActorRef].get
val brother = (supervisor ? Props(new Actor {
watch(failed)
def receive = Actor.emptyBehavior
})).as[ActorRef].get
startWatching(brother)
failed ! Kill
val result = receiveWhile(3 seconds, messages = 3) {

View file

@ -160,9 +160,9 @@ class DeployerSpec extends AkkaSpec(DeployerSpec.deployerConf) {
nr-of-instances = boom
}
}
""", ConfigParseOptions.defaults)
""", ConfigParseOptions.defaults).withFallback(AkkaSpec.testConf)
ActorSystem("invalid", invalidDeployerConf)
ActorSystem("invalid", invalidDeployerConf).stop()
}
}

View file

@ -195,40 +195,45 @@ class FSMActorSpec extends AkkaSpec(Map("akka.actor.debug.fsm" -> true)) with Im
import scala.collection.JavaConverters._
val config = ConfigFactory.parseMap(Map("akka.loglevel" -> "DEBUG",
"akka.actor.debug.fsm" -> true).asJava).withFallback(system.settings.config)
new TestKit(ActorSystem("fsmEvent", config)) {
EventFilter.debug(occurrences = 5) intercept {
val fsm = TestActorRef(new Actor with LoggingFSM[Int, Null] {
startWith(1, null)
when(1) {
case Ev("go")
setTimer("t", Shutdown, 1.5 seconds, false)
goto(2)
val fsmEventSystem = ActorSystem("fsmEvent", config)
try {
new TestKit(fsmEventSystem) {
EventFilter.debug(occurrences = 5) intercept {
val fsm = TestActorRef(new Actor with LoggingFSM[Int, Null] {
startWith(1, null)
when(1) {
case Ev("go")
setTimer("t", Shutdown, 1.5 seconds, false)
goto(2)
}
when(2) {
case Ev("stop")
cancelTimer("t")
stop
}
onTermination {
case StopEvent(r, _, _) testActor ! r
}
})
val name = fsm.path.toString
system.eventStream.subscribe(testActor, classOf[Logging.Debug])
fsm ! "go"
expectMsgPF(1 second, hint = "processing Event(go,null)") {
case Logging.Debug(`name`, s: String) if s.startsWith("processing Event(go,null) from Actor[") true
}
when(2) {
case Ev("stop")
cancelTimer("t")
stop
expectMsg(1 second, Logging.Debug(name, "setting timer 't'/1500 milliseconds: Shutdown"))
expectMsg(1 second, Logging.Debug(name, "transition 1 -> 2"))
fsm ! "stop"
expectMsgPF(1 second, hint = "processing Event(stop,null)") {
case Logging.Debug(`name`, s: String) if s.startsWith("processing Event(stop,null) from Actor[") true
}
onTermination {
case StopEvent(r, _, _) testActor ! r
}
})
val name = fsm.path.toString
system.eventStream.subscribe(testActor, classOf[Logging.Debug])
fsm ! "go"
expectMsgPF(1 second, hint = "processing Event(go,null)") {
case Logging.Debug(`name`, s: String) if s.startsWith("processing Event(go,null) from Actor[") true
expectMsgAllOf(1 second, Logging.Debug(name, "canceling timer 't'"), Normal)
expectNoMsg(1 second)
system.eventStream.unsubscribe(testActor)
}
expectMsg(1 second, Logging.Debug(name, "setting timer 't'/1500 milliseconds: Shutdown"))
expectMsg(1 second, Logging.Debug(name, "transition 1 -> 2"))
fsm ! "stop"
expectMsgPF(1 second, hint = "processing Event(stop,null)") {
case Logging.Debug(`name`, s: String) if s.startsWith("processing Event(stop,null) from Actor[") true
}
expectMsgAllOf(1 second, Logging.Debug(name, "canceling timer 't'"), Normal)
expectNoMsg(1 second)
system.eventStream.unsubscribe(testActor)
}
} finally {
fsmEventSystem.stop()
}
}

View file

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

View file

@ -0,0 +1,8 @@
/**
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.actor
import org.scalatest.junit.JUnitSuite
class JavaAPISpec extends JavaAPI with JUnitSuite

View file

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

View file

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

View file

@ -341,9 +341,11 @@ abstract class ActorModelSpec extends AkkaSpec {
val cachedMessage = CountDownNStop(new CountDownLatch(num))
val stopLatch = new CountDownLatch(num)
val waitTime = (30 seconds).dilated.toMillis
val boss = actorOf(Props(context {
case "run" for (_ 1 to num) (context.self startsWatching context.actorOf(props)) ! cachedMessage
case Terminated(child) stopLatch.countDown()
val boss = actorOf(Props(new Actor {
def receive = {
case "run" for (_ 1 to num) (watch(context.actorOf(props))) ! cachedMessage
case Terminated(child) stopLatch.countDown()
}
}).withDispatcher(system.dispatcherFactory.newPinnedDispatcher("boss")))
boss ! "run"
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 scala.collection.JavaConverters._
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
class DispatchersSpec extends AkkaSpec {
@ -41,7 +40,7 @@ class DispatchersSpec extends AkkaSpec {
throughput = 17
}
}
""", ConfigParseOptions.defaults)
""")
lazy val allDispatchers: Map[String, Option[MessageDispatcher]] = {
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 com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import scala.collection.JavaConverters._
import akka.util.duration._
import akka.util.Duration
@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 {
"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.actor.{ Actor, ActorRef, ActorSystemImpl }
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import scala.collection.JavaConverters._
import akka.actor.ActorSystem
@ -19,7 +18,7 @@ object EventStreamSpec {
loglevel = INFO
event-handlers = ["akka.event.EventStreamSpec$MyLog", "%s"]
}
""".format(Logging.StandardOutLoggerName), ConfigParseOptions.defaults)
""".format(Logging.StandardOutLoggerName))
case class M(i: Int)

View file

@ -1,18 +1,26 @@
/**
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.actor
package akka.event
import org.scalatest.{ BeforeAndAfterAll, BeforeAndAfterEach }
import akka.util.duration._
import akka.testkit._
import org.scalatest.WordSpec
import akka.event.Logging
import akka.util.Duration
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import scala.collection.JavaConverters._
import java.util.Properties
import akka.actor.Actor
import akka.actor.ActorSystem
import akka.actor.HotSwap
import akka.actor.UnhandledMessageException
import akka.actor.PoisonPill
import akka.actor.ActorSystemImpl
import akka.actor.Props
import akka.actor.OneForOneStrategy
import akka.actor.ActorKilledException
import akka.actor.Kill
object LoggingReceiveSpec {
class TestLogActor extends Actor {
@ -58,7 +66,7 @@ class LoggingReceiveSpec extends WordSpec with BeforeAndAfterEach with BeforeAnd
val r: Actor.Receive = {
case null
}
val log = Actor.LoggingReceive("funky", r)
val log = LoggingReceive("funky")(r)
log.isDefinedAt("hallo")
expectMsg(1 second, Logging.Debug("funky", "received unhandled message hallo"))
}
@ -70,7 +78,7 @@ class LoggingReceiveSpec extends WordSpec with BeforeAndAfterEach with BeforeAnd
system.eventStream.subscribe(testActor, classOf[Logging.Debug])
system.eventStream.subscribe(testActor, classOf[Logging.Error])
val actor = TestActorRef(new Actor {
def receive = loggable(this) {
def receive = LoggingReceive(this) {
case x
sender ! "x"
}
@ -100,7 +108,7 @@ class LoggingReceiveSpec extends WordSpec with BeforeAndAfterEach with BeforeAnd
new TestKit(appLogging) with ImplicitSender {
system.eventStream.subscribe(testActor, classOf[Logging.Debug])
val actor = TestActorRef(new Actor {
def receive = loggable(this)(loggable(this) {
def receive = LoggingReceive(this)(LoggingReceive(this) {
case _ sender ! "x"
})
})

View file

@ -34,7 +34,7 @@ class TellThroughput10000PerformanceSpec extends PerformanceSpec {
def createDispatcher(name: String) = ThreadPoolConfigDispatcherBuilder(config
new Dispatcher(system.dispatcherFactory.prerequisites, name, 10000,
Duration.Zero, UnboundedMailbox(), config, Duration(60, TimeUnit.SECONDS)), ThreadPoolConfig())
Duration.Zero, UnboundedMailbox(), config, Duration(1, TimeUnit.SECONDS)), ThreadPoolConfig())
.withNewThreadPoolWithLinkedBlockingQueueWithUnboundedCapacity
.setCorePoolSize(maxClients * 2)
.build

View file

@ -14,7 +14,7 @@ class TellThroughputComputationPerformanceSpec extends PerformanceSpec {
def createDispatcher(name: String) = ThreadPoolConfigDispatcherBuilder(config
new Dispatcher(system.dispatcherFactory.prerequisites, name, 5,
Duration.Zero, UnboundedMailbox(), config, 60 seconds), ThreadPoolConfig())
Duration.Zero, UnboundedMailbox(), config, 1 seconds), ThreadPoolConfig())
.withNewThreadPoolWithLinkedBlockingQueueWithUnboundedCapacity
.setCorePoolSize(maxClients)
.build

View file

@ -14,7 +14,7 @@ class TellThroughputPerformanceSpec extends PerformanceSpec {
def createDispatcher(name: String) = ThreadPoolConfigDispatcherBuilder(config
new Dispatcher(system.dispatcherFactory.prerequisites, name, 5,
Duration.Zero, UnboundedMailbox(), config, 60 seconds), ThreadPoolConfig())
Duration.Zero, UnboundedMailbox(), config, 1 seconds), ThreadPoolConfig())
.withNewThreadPoolWithLinkedBlockingQueueWithUnboundedCapacity
.setCorePoolSize(maxClients)
.build

View file

@ -18,7 +18,7 @@ class TellThroughputSeparateDispatchersPerformanceSpec extends PerformanceSpec {
def createDispatcher(name: String) = ThreadPoolConfigDispatcherBuilder(config
new Dispatcher(system.dispatcherFactory.prerequisites, name, 5,
Duration.Zero, UnboundedMailbox(), config, Duration(60, TimeUnit.SECONDS)), ThreadPoolConfig())
Duration.Zero, UnboundedMailbox(), config, Duration(1, TimeUnit.SECONDS)), ThreadPoolConfig())
.withNewThreadPoolWithLinkedBlockingQueueWithUnboundedCapacity
.setCorePoolSize(1)
.build

View file

@ -222,7 +222,7 @@ class Report(
sb.append("Akka version: ").append(system.settings.ConfigVersion)
sb.append("\n")
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)
}

View file

@ -11,7 +11,6 @@ import akka.actor.{ ActorSystem, ActorSystemImpl }
import java.io.{ ObjectInputStream, ByteArrayInputStream, ByteArrayOutputStream, ObjectOutputStream }
import akka.actor.DeadLetterActorRef
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
object SerializeSpec {
@ -32,7 +31,7 @@ object SerializeSpec {
}
}
}
""", ConfigParseOptions.defaults)
""")
@BeanInfo
case class Address(no: String, street: String, city: String, zip: String) { def this() = this("", "", "", "") }
@ -98,15 +97,19 @@ class SerializeSpec extends AkkaSpec(SerializeSpec.serializationConf) {
"serialize DeadLetterActorRef" in {
val outbuf = new ByteArrayOutputStream()
val out = new ObjectOutputStream(outbuf)
val a = ActorSystem()
out.writeObject(a.deadLetters)
out.flush()
out.close()
val a = ActorSystem("SerializeDeadLeterActorRef", AkkaSpec.testConf)
try {
out.writeObject(a.deadLetters)
out.flush()
out.close()
val in = new ObjectInputStream(new ByteArrayInputStream(outbuf.toByteArray))
Serialization.currentSystem.withValue(a.asInstanceOf[ActorSystemImpl]) {
val deadLetters = in.readObject().asInstanceOf[DeadLetterActorRef]
(deadLetters eq a.deadLetters) must be(true)
val in = new ObjectInputStream(new ByteArrayInputStream(outbuf.toByteArray))
Serialization.currentSystem.withValue(a.asInstanceOf[ActorSystemImpl]) {
val deadLetters = in.readObject().asInstanceOf[DeadLetterActorRef]
(deadLetters eq a.deadLetters) must be(true)
}
} finally {
a.stop()
}
}
}

View file

@ -0,0 +1,8 @@
/**
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.util
import org.scalatest.junit.JUnitSuite
class JavaDurationSpec extends JavaDuration with JUnitSuite

View file

@ -12,8 +12,8 @@ final class AbstractMailbox {
static {
try {
mailboxStatusOffset = Unsafe.instance.objectFieldOffset(Mailbox.class.getDeclaredField("_status"));
systemMessageOffset = Unsafe.instance.objectFieldOffset(Mailbox.class.getDeclaredField("_systemQueue"));
mailboxStatusOffset = Unsafe.instance.objectFieldOffset(Mailbox.class.getDeclaredField("_statusDoNotCallMeDirectly"));
systemMessageOffset = Unsafe.instance.objectFieldOffset(Mailbox.class.getDeclaredField("_systemQueueDoNotCallMeDirectly"));
} catch(Throwable t){
throw new ExceptionInInitializerError(t);
}

View file

@ -6,69 +6,239 @@ package com.typesafe.config;
import java.util.List;
/**
* This class represents an immutable map from config paths to config values. It
* also contains some static methods for creating configs.
* An immutable map from config paths to config values.
*
* <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
* 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
* keys. 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.
* keys. Path expressions are described in the <a
* href="https://github.com/havocp/config/blob/master/HOCON.md">spec for
* Human-Optimized Config Object Notation</a>. 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.
*
* The API for a Config is in terms of path expressions, while the API for a
* ConfigObject is in terms of keys. Conceptually, Config is a one-level map
* from paths to values, while a ConfigObject is a tree of maps from keys to
* values.
* <p>
* The API for a {@code Config} is in terms of path expressions, while the API
* for a {@code ConfigObject} is in terms of keys. Conceptually, {@code Config}
* 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,
* ConfigValue with valueType() of ConfigValueType.NULL exist in a ConfigObject,
* while a Config treats null values as if they were missing.
* <p>
* Another difference between {@code Config} and {@code ConfigObject} is that
* 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,
* nor do they return a ConfigValue with valueType() of ConfigValueType.NULL.
* Instead, they throw ConfigException.Missing if the value is completely absent
* or set to null. If the value is set to null, a subtype of
* ConfigException.Missing called ConfigException.Null will be thrown.
* 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.
* <p>
* The "getters" on a {@code Config} all work in the same way. They never return
* null, nor do they return a {@code ConfigValue} with
* {@link ConfigValue#valueType() valueType()} of {@link ConfigValueType#NULL
* NULL}. Instead, they throw {@link ConfigException.Missing} if the value is
* completely absent or set to null. If the value is set to null, a subtype of
* {@code ConfigException.Missing} called {@link ConfigException.Null} will be
* 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
* ConfigObject with toObject, and then iterate over the ConfigObject.
* <p>
* 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 {
/**
* Gets the config as a tree of ConfigObject. This is a constant-time
* operation (it is not proportional to the number of values in the Config).
* Gets the {@code Config} as a tree of {@link ConfigObject}. This is a
* 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();
@Override
Config withFallback(ConfigMergeable other);
@Override
ConfigObject toValue();
/**
* Returns a replacement config with all substitutions (the
* <code>${foo.bar}</code> syntax, see <a
* href="https://github.com/havocp/config/blob/master/HOCON.md">the
* spec</a>) resolved. Substitutions are looked up using this
* <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>.
*
* <p>
* This method uses {@link ConfigResolveOptions#defaults()}, there is
* 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 ConfigObject.containsKey(): it looks for a path
* expression, not a key; and it returns false for null values, while
* containsKey() returns true indicating that the object contains a null
* value for the key.
* 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.
*
* If a path exists according to hasPath(), then getValue() will never throw
* an exception. However, the typed getters, such as getInt(), will still
* throw if the value is not convertible to the requested type.
* <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
* the path expression
@ -78,12 +248,19 @@ public interface Config extends ConfigMergeable {
*/
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();
/**
*
* @param path
* @return
* path expression
* @return the boolean value at the requested path
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType
@ -93,7 +270,8 @@ public interface Config extends ConfigMergeable {
/**
* @param path
* @return
* path expression
* @return the numeric value at the requested path
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType
@ -103,17 +281,20 @@ public interface Config extends ConfigMergeable {
/**
* @param path
* @return
* path expression
* @return the 32-bit integer value at the requested path
* @throws ConfigException.Missing
* if value is absent or null
* @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);
/**
* @param path
* @return
* path expression
* @return the 64-bit long value at the requested path
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType
@ -123,7 +304,8 @@ public interface Config extends ConfigMergeable {
/**
* @param path
* @return
* path expression
* @return the floating-point value at the requested path
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType
@ -133,7 +315,8 @@ public interface Config extends ConfigMergeable {
/**
* @param path
* @return
* path expression
* @return the string value at the requested path
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType
@ -143,7 +326,8 @@ public interface Config extends ConfigMergeable {
/**
* @param path
* @return
* path expression
* @return the {@link ConfigObject} value at the requested path
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType
@ -153,7 +337,8 @@ public interface Config extends ConfigMergeable {
/**
* @param path
* @return
* path expression
* @return the nested {@code Config} value at the requested path
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType
@ -162,9 +347,13 @@ public interface Config extends ConfigMergeable {
Config getConfig(String path);
/**
* Gets the value at the path as an unwrapped Java boxed value (Boolean,
* Integer, Long, etc.)
* Gets the value at the path as an unwrapped Java boxed value (
* {@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
* if value is absent or null
*/
@ -172,35 +361,47 @@ public interface Config extends ConfigMergeable {
/**
* 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()
* from the Map interface if you want an unprocessed value.
* missing, in which case it throws just like the other getters. Use
* {@code get()} from the {@link java.util.Map Map} interface if you want an
* unprocessed value.
*
* @param path
* @return
* path expression
* @return the value at the requested path
* @throws ConfigException.Missing
* if value is absent or null
*/
ConfigValue getValue(String path);
/**
* Get value as a size in bytes (parses special strings like "128M"). The
* size units are interpreted as for memory, not as for disk space, so they
* are in powers of two.
* Gets a value as a size in bytes (parses special strings like "128M"). If
* the value is already a number, then it's left alone; if it's a string,
* 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
* if value is absent or null
* @throws ConfigException.WrongType
* if value is not convertible to Long or String
* @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
* 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
* if value is absent or null
* @throws ConfigException.WrongType
@ -213,8 +414,12 @@ public interface Config extends ConfigMergeable {
/**
* 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
* 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
* if value is absent or null
* @throws ConfigException.WrongType
@ -225,13 +430,13 @@ public interface Config extends ConfigMergeable {
Long getNanoseconds(String path);
/**
* Gets a list value (with any element type) as a ConfigList, which
* implements java.util.List<ConfigValue>. Throws if the path is unset or
* null.
* Gets a list value (with any element type) as a {@link ConfigList}, which
* implements {@code java.util.List<ConfigValue>}. Throws if the path is
* unset or null.
*
* @param path
* the path to the list value.
* @return the ConfigList at the path
* @return the {@link ConfigList} at the path
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType
@ -257,7 +462,7 @@ public interface Config extends ConfigMergeable {
List<? extends Object> getAnyRefList(String path);
List<Long> getMemorySizeInBytesList(String path);
List<Long> getBytesList(String path);
List<Long> getMillisecondsList(String path);

View file

@ -3,15 +3,19 @@
*/
package com.typesafe.config;
/**
* All exceptions thrown by the library are subclasses of ConfigException.
*/
public class ConfigException extends RuntimeException {
public abstract class ConfigException extends RuntimeException {
private static final long serialVersionUID = 1L;
final private ConfigOrigin origin;
protected ConfigException(ConfigOrigin origin, String message,
Throwable cause) {
super(origin.description() + ": " + message, cause);
this.origin = origin;
}
protected ConfigException(ConfigOrigin origin, String message) {
@ -20,12 +24,26 @@ public class ConfigException extends RuntimeException {
protected ConfigException(String message, Throwable cause) {
super(message, cause);
this.origin = null;
}
protected ConfigException(String message) {
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
* requested.
@ -134,6 +152,11 @@ public class ConfigException extends RuntimeException {
}
}
/**
* Exception indicating that a path expression was invalid. Try putting
* double quotes around path elements that contain "special" characters.
*
*/
public static class BadPath extends ConfigException {
private static final long serialVersionUID = 1L;
@ -163,10 +186,11 @@ public class ConfigException extends RuntimeException {
}
/**
* Exception indicating that there's a bug in something or the runtime
* environment is broken. This exception should never be handled; instead,
* something should be fixed to keep the exception from occurring.
*
* Exception indicating that there's a bug in something (possibly the
* library itself) or the runtime environment is broken. This exception
* 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 {
private static final long serialVersionUID = 1L;
@ -212,12 +236,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
* substitutions to be resolved, but substitutions have not been resolved.
* This is always a bug in either application code or the library; it's
* wrong to write a handler for this exception because you should be able to
* fix the code to avoid it.
* substitutions to be resolved, but substitutions have not been resolved
* (that is, {@link Config#resolve} was not called). This is always a bug in
* either application code or the library; it's wrong to write a handler for
* 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 {
private static final long serialVersionUID = 1L;
@ -230,4 +271,92 @@ public class ConfigException extends RuntimeException {
this(message, null);
}
}
/**
* Information about a problem that occurred in {@link Config#checkValid}. A
* {@link ConfigException.ValidationFailed} exception thrown from
* <code>checkValid()</code> includes a list of problems encountered.
*/
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;
}
/** Returns the config setting causing the problem. */
public String path() {
return path;
}
/**
* Returns where the problem occurred (origin may include info on the
* file, line number, etc.).
*/
public ConfigOrigin origin() {
return origin;
}
/** Returns a description of the problem. */
public String problem() {
return problem;
}
}
/**
* Exception indicating that {@link Config#checkValid} found validity
* problems. The problems are available via the {@link #problems()} method.
* The <code>getMessage()</code> of this exception is a potentially very
* long string listing all the problems found.
*/
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();
}
}
/**
* Exception that doesn't fall into any other category.
*/
public static class Generic extends ConfigException {
private static final long serialVersionUID = 1L;
public Generic(String message, Throwable cause) {
super(message, cause);
}
public Generic(String message) {
this(message, null);
}
}
}

View file

@ -5,211 +5,484 @@ package com.typesafe.config;
import java.io.File;
import java.io.Reader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
import java.util.Properties;
import com.typesafe.config.impl.ConfigImpl;
import com.typesafe.config.impl.ConfigUtil;
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
* operation potentially parsing multiple resources and resolving substitutions,
* while the ones with "parse" in the name just create a ConfigValue from a
* resource and nothing else.
* while the ones with "parse" in the name just create a {@link ConfigValue}
* from a resource and nothing else.
*/
public final class ConfigFactory {
private ConfigFactory() {
}
/**
* Loads a configuration for the given root path in a "standard" way.
* Oversimplified, if your root path is foo.bar then this will load files
* from the classpath: foo-bar.conf, foo-bar.json, foo-bar.properties,
* foo-bar-reference.conf, foo-bar-reference.json,
* foo-bar-reference.properties. It will override all those files with any
* system properties that begin with "foo.bar.", as well.
*
* 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.
* Loads an application's configuration from the given classpath resource or
* classpath resource basename, sandwiches it between default reference
* config and default overrides, and then resolves it. The classpath
* resource is "raw" (it should have no "/" prefix, and is not made relative
* to any package, so it's like {@link ClassLoader#getResource} not
* {@link Class#getResource}).
*
* <p>
* The loaded object will already be resolved (substitutions have already
* 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
* you want to parse additional files or something then you need to use
* loadWithoutResolving().
* {@link #load(Config)}.
*
* @param rootPath
* the configuration "domain"
* @return configuration object for the requested root path
* @param resourceBasename
* name (optionally without extension) of a resource on classpath
* @return configuration for an application
*/
public static ConfigRoot load(String rootPath) {
return loadWithoutResolving(rootPath).resolve();
}
public static ConfigRoot load(String rootPath,
ConfigParseOptions parseOptions, ConfigResolveOptions resolveOptions) {
return loadWithoutResolving(rootPath, parseOptions).resolve(
resolveOptions);
public static Config load(String resourceBasename) {
return load(resourceBasename, ConfigParseOptions.defaults(),
ConfigResolveOptions.defaults());
}
/**
* Like load() but does not resolve the object, so you can go ahead and add
* more fallbacks and stuff and have them seen by substitutions when you do
* call {@link ConfigRoot.resolve()}.
*
* @param rootPath
* @return
* Like {@link #load(String)} but allows you to specify parse and resolve
* options.
* @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) {
return loadWithoutResolving(rootPath, ConfigParseOptions.defaults());
public static Config load(String resourceBasename, ConfigParseOptions parseOptions,
ConfigResolveOptions resolveOptions) {
Config appConfig = ConfigFactory.parseResourcesAnySyntax(ConfigFactory.class, "/"
+ resourceBasename, parseOptions);
return load(appConfig, resolveOptions);
}
public static ConfigRoot loadWithoutResolving(String rootPath,
ConfigParseOptions options) {
ConfigRoot system = systemPropertiesRoot(rootPath);
Config mainFiles = parseResourcesForPath(rootPath, options);
Config referenceFiles = parseResourcesForPath(rootPath + ".reference",
options);
return system.withFallback(mainFiles).withFallback(referenceFiles);
/**
* Assembles a standard configuration using a custom <code>Config</code>
* object rather than loading "application.conf". The <code>Config</code>
* object will be sandwiched between the default reference config and
* default overrides and then resolved.
*
* @param config
* the application's portion of the configuration
* @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}.
*
* @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 {
private static Config loadDefaultConfig() {
int specified = 0;
// override application.conf with config.file, config.resource,
// config.url if requested.
String resource = System.getProperty("config.resource");
if (resource != null)
specified += 1;
String file = System.getProperty("config.file");
if (file != null)
specified += 1;
String url = System.getProperty("config.url");
if (url != null)
specified += 1;
if (specified == 0) {
return load("application");
} else if (specified > 1) {
throw new ConfigException.Generic("You set more than one of config.file='" + file
+ "', config.url='" + url + "', config.resource='" + resource
+ "'; don't know which one to use!");
} else {
if (resource != null) {
// this deliberately does not parseResourcesAnySyntax; if
// people want that they can use an include statement.
return load(parseResources(ConfigFactory.class, resource));
} else if (file != null) {
return load(parseFile(new File(file)));
} else {
try {
return load(parseURL(new URL(url)));
} catch (MalformedURLException e) {
throw new ConfigException.Generic(
"Bad URL in config.url system property: '" + url + "': "
+ e.getMessage(), e);
}
}
}
}
static final Config defaultConfig = loadDefaultConfig();
}
/**
* Loads a default configuration, equivalent to {@link #load(String)
* load("application")} in most cases. This configuration should be used by
* libraries and frameworks unless an application provides a different one.
* <p>
* This method may return a cached singleton.
* <p>
* If the system properties <code>config.resource</code>,
* <code>config.file</code>, or <code>config.url</code> are set, then the
* classpath resource, file, or URL specified in those properties will be
* used rather than the default
* <code>application.{conf,json,properties}</code> classpath resources.
* These system properties should not be set in code (after all, you can
* just parse whatever you want manually and then use {@link #load(Config)
* if you don't want to use <code>application.conf</code>}). The properties
* are intended for use by the person or script launching the application.
* For example someone might have a <code>production.conf</code> that
* include <code>application.conf</code> but then change a couple of values.
* When launching the app they could specify
* <code>-Dconfig.resource=production.conf</code> to get production mode.
* <p>
* If no system properties are set to change the location of the default
* configuration, <code>ConfigFactory.load()</code> is equivalent to
* <code>ConfigFactory.load("application")</code>.
*
* @return configuration for an application
*/
public static Config load() {
try {
return DefaultConfigHolder.defaultConfig;
} catch (ExceptionInInitializerError e) {
throw ConfigUtil.extractInitializerError(e);
}
}
/**
* 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() {
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) {
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() {
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() {
return ConfigImpl.envVariablesAsConfig();
}
/**
* Converts a Java Properties object to a ConfigObject using the rules
* documented in https://github.com/havocp/config/blob/master/HOCON.md The
* keys in the Properties object are split on the period character '.' and
* treated as paths. The values will all end up as string values. If you
* have both "a=foo" and "a.b=bar" in your properties file, so "a" is both
* the object containing "b" and the string "foo", then the string value is
* dropped.
* Converts a Java {@link java.util.Properties} object to a
* {@link ConfigObject} using the rules documented in the <a
* href="https://github.com/havocp/config/blob/master/HOCON.md">HOCON
* spec</a>. The keys in the <code>Properties</code> object are split on the
* period character '.' and treated as paths. The values will all end up as
* string values. If you have both "a=foo" and "a.b=bar" in your properties
* 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
* to use the systemProperties() or systemPropertiesRoot() methods. Those
* methods are able to use a cached global singleton ConfigObject for the
* system properties.
* <p>
* If you want to have <code>System.getProperties()</code> as a
* ConfigObject, it's better to use the {@link #systemProperties()} method
* which returns a cached global singleton.
*
* @param properties
* a Java Properties object
* @param options
* @return
* @return the parsed configuration
*/
public static Config parseProperties(Properties properties,
ConfigParseOptions options) {
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) {
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) {
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) {
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,
* just parses it according to that extension. If the fileBasename does not
* end in an extension, then parse all known extensions and merge whatever
* is found. If options force a specific syntax, only parse files with an
* extension matching that syntax. If options.getAllowMissing() is true,
* then no files have to exist; if false, then at least one file has to
* exist.
* Parses a file with a flexible extension. If the <code>fileBasename</code>
* already ends in a known extension, this method parses it according to
* that extension (the file's syntax must match its extension). If the
* <code>fileBasename</code> does not end in an extension, it parses files
* with all known extensions and merges whatever is found.
*
* <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
* a filename with or without extension
* @param options
* @return
* parse options
* @return the parsed configuration
*/
public static Config parseFileAnySyntax(File fileBasename,
ConfigParseOptions options) {
return ConfigImpl.parseFileAnySyntax(fileBasename, options).toConfig();
}
public static Config parseResource(Class<?> klass, String resource,
ConfigParseOptions options) {
return Parseable.newResource(klass, resource, options).parse()
.toConfig();
public static Config parseFileAnySyntax(File fileBasename) {
return parseFileAnySyntax(fileBasename, ConfigParseOptions.defaults());
}
/**
* Same behavior as parseFileAnySyntax() but for classpath resources
* instead.
* Parses all resources on the classpath with the given name and merges them
* 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 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
* @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) {
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();
}
public static Config parseResourcesAnySyntax(Class<?> klass, String resourceBasename) {
return parseResourcesAnySyntax(klass, resourceBasename, ConfigParseOptions.defaults());
}
public static Config parseString(String s, ConfigParseOptions options) {
return Parseable.newString(s, options).parse().toConfig();
}
/**
* Parses classpath resources corresponding to this path expression.
* 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();
public static Config parseString(String s) {
return parseString(s, ConfigParseOptions.defaults());
}
/**
* Similar to ConfigValueFactory.fromMap(), but the keys in the map are path
* expressions, rather than keys; and correspondingly it returns a Config
* instead of a 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.
* Creates a {@code Config} based on a {@link java.util.Map} from paths to
* plain Java values. Similar to
* {@link ConfigValueFactory#fromMap(Map,String)}, except the keys in the
* map are path expressions, rather than keys; and correspondingly it
* 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)
* 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
@ -221,7 +494,7 @@ public final class ConfigFactory {
* description of what this map represents, like a filename, or
* "default settings" (origin description is used in error
* messages)
* @return
* @return the map converted to a {@code Config}
*/
public static Config parseMap(Map<String, ? extends Object> values,
String originDescription) {
@ -229,11 +502,11 @@ public final class ConfigFactory {
}
/**
* See the other overload of parseMap() for details, this one just uses a
* default origin description.
* See the other overload of {@link #parseMap(Map, String)} for details,
* this one just uses a default origin description.
*
* @param values
* @return
* @return the map converted to a {@code Config}
*/
public static Config parseMap(Map<String, ? extends Object> values) {
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
* intended for apps to implement.
* Context provided to a {@link ConfigIncluder}; this interface is only useful
* inside a {@code ConfigIncluder} implementation, and is not intended for apps
* to implement.
*/
public interface ConfigIncludeContext {
/**
@ -15,7 +16,7 @@ public interface ConfigIncludeContext {
* 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
* compute what the name would be.
*
*
* 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.)

View file

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

View file

@ -6,9 +6,30 @@ package com.typesafe.config;
import java.util.List;
/**
* A list (aka array) value corresponding to ConfigValueType.LIST or JSON's
* "[1,2,3]" value. Implements java.util.List<ConfigValue> so you can use it
* like a regular Java list.
* Subtype of {@link ConfigValue} representing a list value, as in JSON's
* {@code [1,2,3]} syntax.
*
* <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 {

View file

@ -4,33 +4,39 @@
package com.typesafe.config;
/**
* This is a marker for types that can be merged as a fallback into a Config or
* a ConfigValue. Both Config and ConfigValue are mergeable.
* Marker for types whose instances can be merged, that is {@link Config} and
* {@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 {
/**
* Converts the mergeable to a ConfigValue to be merged.
*
* @return
*/
ConfigValue toValue();
/**
* Returns a new value computed by merging this value with another, with
* keys in this value "winning" over the other one. Only ConfigObject and
* Config instances do anything in this method (they need to merge the
* fallback keys into themselves). All other 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
*
* Note that objects do not merge "across" non-objects; if you do
* keys in this value "winning" over the other one. Only
* {@link ConfigObject} and {@link Config} instances do anything in this
* method (they need to merge the fallback keys into themselves). All other
* values just return the original value, since they automatically override
* any fallback.
*
* <p>
* The semantics of merging are described in the <a
* 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>,
* then <code>otherObject</code> will simply be ignored. This is an
* intentional part of how merging works. Both non-objects, and any object
* which has fallen back to a non-object, block subsequent fallbacks.
*
*
* @param other
* an object whose keys should be used if the keys are not
* present in this one

View file

@ -6,54 +6,67 @@ package com.typesafe.config;
import java.util.Map;
/**
* A ConfigObject is a read-only configuration object, which may have nested
* child objects. Implementations of ConfigObject should be immutable (at least
* from the perspective of anyone using this interface) and thus thread-safe.
* Subtype of {@link ConfigValue} representing an object (dictionary, map)
* value, as in JSON's <code>{ "a" : 42 }</code> syntax.
*
* In most cases you want to use the Config interface rather than this one. Call
* toConfig() to convert a ConfigObject to a config.
* <p>
* {@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
* in terms of path expressions. Conceptually, ConfigObject is a tree of maps
* from keys to values, while a ConfigObject is a one-level map from paths to
* values.
* <p>
* Like all {@link ConfigValue} subtypes, {@code ConfigObject} is immutable.
* This makes it threadsafe and you never have to create "defensive copies." The
* 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
* 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
* 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.
* <p>
* The {@link ConfigValue#valueType} method on an object returns
* {@link ConfigValueType#OBJECT}.
*
* ConfigObject implements java.util.Map<String,ConfigValue> and all methods
* work with keys, not path expressions.
* <p>
* 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
* methods all throw UnsupportedOperationException. This Map is immutable.
* <p>
* 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() ==
* ConfigValueType.NULL. If get() returns Java's null then the key was not
* present in the parsed file (or wherever this value tree came from). If get()
* returns a ConfigValue with type ConfigValueType.NULL then the key was set to
* null explicitly.
* <p>
* A {@code ConfigObject} may contain null values, which will have
* {@link ConfigValue#valueType()} equal to {@link ConfigValueType#NULL}. If
* {@code get()} returns Java's null then the key was not present in the parsed
* 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> {
/**
* Converts this object to a Config instance, enabling you to use path
* expressions to find values in the object. This is a constant-time
* Converts this object to a {@link Config} instance, enabling you to use
* path expressions to find values in the object. This is a constant-time
* operation (it is not proportional to the size of the object).
*
* @return
* @return a {@link Config} with this object as its root
*/
Config toConfig();
/**
* Recursively unwraps the object, returning a map from String to whatever
* plain Java values are unwrapped from the object's values.
*
* @return a {@link java.util.Map} containing plain Java objects
*/
@Override
Map<String, Object> unwrapped();
@ -62,10 +75,15 @@ public interface ConfigObject extends ConfigValue, Map<String, ConfigValue> {
ConfigObject withFallback(ConfigMergeable other);
/**
* Gets a ConfigValue at the given key, or returns null if there is no
* value. The returned ConfigValue may have ConfigValueType.NULL or any
* other type, and the passed-in key must be a key in this object, rather
* than a path expression.
* Gets a {@link ConfigValue} at the given key, or returns null if there is
* no value. The returned {@link ConfigValue} may have
* {@link ConfigValueType#NULL} or any other type, and the passed-in key
* 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
ConfigValue get(Object key);

View file

@ -3,10 +3,67 @@
*/
package com.typesafe.config;
import java.net.URL;
/**
* ConfigOrigin is used to track the origin (such as filename and line number)
* of a ConfigValue or other object. The origin is used in error messages.
* Represents the origin (such as filename and line number) of a
* {@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 {
/**
* Returns a string describing the origin of a value or exception. This will
* never return null.
*
* @return string describing the origin
*/
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;
/**
* 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 {
final ConfigSyntax syntax;
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
* filename extension; if guessing fails, assume ConfigSyntax.CONF.
*
* filename extension; if guessing fails, assume {@link ConfigSyntax#CONF}.
*
* @param syntax
* @return
* a syntax or {@code null} for best guess
* @return options with the syntax set
*/
public ConfigParseOptions setSyntax(ConfigSyntax 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 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
* 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
* @return
* @return options with the origin description set
*/
public ConfigParseOptions setOriginDescription(String originDescription) {
if (this.originDescription == originDescription)
@ -79,7 +97,7 @@ public final class ConfigParseOptions {
* case.
*
* @param allowMissing
* @return
* @return options with the "allow missing" flag set
*/
public ConfigParseOptions setAllowMissing(boolean allowMissing) {
if (this.allowMissing == allowMissing)

View file

@ -3,21 +3,40 @@
*/
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 {
/**
* 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
* 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);
/** 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();
}

View file

@ -3,36 +3,70 @@
*/
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()
* .setUseSystemEnvironment(false)
* </pre>
* <p>
* In addition to {@link ConfigResolveOptions#defaults}, there's a prebuilt
* {@link ConfigResolveOptions#noSystem} which avoids looking at any system
* environment variables or other external system information. (Right now,
* environment variables are the only example.)
*/
public final class ConfigResolveOptions {
private final boolean useSystemProperties;
private final boolean useSystemEnvironment;
private ConfigResolveOptions(boolean useSystemProperties,
boolean useSystemEnvironment) {
this.useSystemProperties = useSystemProperties;
private ConfigResolveOptions(boolean useSystemEnvironment) {
this.useSystemEnvironment = useSystemEnvironment;
}
/**
* Returns the default resolve options.
*
* @return the default resolve options
*/
public static ConfigResolveOptions defaults() {
return new ConfigResolveOptions(true, true);
return new ConfigResolveOptions(true);
}
/**
* Returns resolve options that disable any reference to "system" data
* (currently, this means environment variables).
*
* @return the resolve options with env variables disabled
*/
public static ConfigResolveOptions noSystem() {
return new ConfigResolveOptions(false, false);
}
public ConfigResolveOptions setUseSystemProperties(boolean value) {
return new ConfigResolveOptions(value, useSystemEnvironment);
return defaults().setUseSystemEnvironment(false);
}
/**
* 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
*/
@SuppressWarnings("static-method")
public ConfigResolveOptions setUseSystemEnvironment(boolean value) {
return new ConfigResolveOptions(useSystemProperties, value);
}
public boolean getUseSystemProperties() {
return useSystemProperties;
return new ConfigResolveOptions(value);
}
/**
* 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() {
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;
/**
* 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 {
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;
/**
* Interface implemented by any configuration value. From the perspective of
* users of this interface, the object is immutable. It is therefore safe to use
* from multiple threads.
* An immutable value, following the <a href="http://json.org">JSON</a> type
* schema.
*
* <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 {
/**
* 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
*/
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
*/
ConfigValueType valueType();
/**
* Returns the config value as a plain Java boxed value, should be a String,
* Number, etc. matching the valueType() of the ConfigValue. If the value is
* a ConfigObject or ConfigList, it is recursively unwrapped.
* Returns the value as a plain Java boxed value, that is, a {@code String},
* {@code Number}, {@code Boolean}, {@code Map<String,Object>},
* {@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();
/**
* 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
ConfigValue withFallback(ConfigMergeable other);
}

View file

@ -3,7 +3,6 @@
*/
package com.typesafe.config;
import java.util.Collection;
import java.util.Map;
import com.typesafe.config.impl.ConfigImpl;
@ -14,6 +13,9 @@ import com.typesafe.config.impl.ConfigImpl;
* data structures.
*/
public final class ConfigValueFactory {
private ConfigValueFactory() {
}
/**
* 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
@ -23,23 +25,27 @@ public final class ConfigValueFactory {
* the Iterable is not an ordered collection, results could be strange,
* since ConfigList is ordered.
*
* <p>
* 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
* object with a key called "foo.bar", rather than an object with a key
* "foo" containing another object with a key "bar".
*
* <p>
* 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
* from, or something short describing the value such as "default settings".
* The originDescription is prefixed to error messages so users can tell
* where problematic values are coming from.
*
* <p>
* Supplying the result of ConfigValue.unwrapped() to this function is
* guaranteed to work and should give you back a ConfigValue that matches
* the one you unwrapped. The re-wrapped ConfigValue will lose some
* information that was present in the original such as its origin, but it
* will have matching values.
*
* <p>
* 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
* 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
* wrapper that only works on Map and returns ConfigObject rather than
* ConfigValue.
* wrapper that only works on {@link java.util.Map} and returns
* {@link ConfigObject} rather than {@link ConfigValue}.
*
* <p>
* 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
* 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
* ConfigObject. The keys will not be parsed or modified, and the values are
* wrapped in ConfigValue. To get nested ConfigObject, some of the values in
* the map would have to be more maps.
* {@code ConfigObject}. The keys will not be parsed or modified, and the
* values are wrapped in ConfigValue. To get nested {@code ConfigObject},
* 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
* path expressions.
* <p>
* See also {@link ConfigFactory#parseMap(Map,String)} which interprets the
* keys in the map as path expressions.
*
* @param values
* @param originDescription
* @return
* @return a new {@link ConfigObject} value
*/
public static ConfigObject fromMap(Map<String, ? extends Object> values,
String originDescription) {
@ -82,12 +90,12 @@ public final class ConfigValueFactory {
/**
* See the fromAnyRef() documentation for details. This is a typesafe
* wrapper that only works on Iterable and returns ConfigList rather than
* ConfigValue.
* wrapper that only works on {@link java.util.Iterable} and returns
* {@link ConfigList} rather than {@link ConfigValue}.
*
* @param values
* @param originDescription
* @return
* @return a new {@link ConfigList} value
*/
public static ConfigList fromIterable(Iterable<? extends Object> values,
String originDescription) {
@ -95,35 +103,39 @@ public final class ConfigValueFactory {
}
/**
* See the other overload of fromAnyRef() for details, this one just uses a
* default origin description.
* See the other overload {@link #fromAnyRef(Object,String)} for details,
* this one just uses a default origin description.
*
* @param object
* @return
* @return a new {@link ConfigValue}
*/
public static ConfigValue fromAnyRef(Object object) {
return fromAnyRef(object, null);
}
/**
* See the other overload of fromMap() for details, this one just uses a
* default origin description.
* See the other overload {@link #fromMap(Map,String)} for details, this one
* 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
* @return
* @return a new {@link ConfigObject}
*/
public static ConfigObject fromMap(Map<String, ? extends Object> values) {
return fromMap(values, null);
}
/**
* See the other overload of fromIterable() for details, this one just uses
* a default origin description.
* See the other overload of {@link #fromIterable(Iterable, String)} for
* details, this one just uses a default origin description.
*
* @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);
}
}

View file

@ -4,7 +4,8 @@
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 {
OBJECT, LIST, NUMBER, BOOLEAN, NULL, STRING

View file

@ -35,6 +35,11 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
return config;
}
@Override
public AbstractConfigObject toFallbackValue() {
return this;
}
/**
* This looks up the key with no transformation or type conversion of any
* kind, and returns null if the key is not present.
@ -61,19 +66,23 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
* (just returns null if path not found). Does however resolve the path, if
* resolver != null.
*/
protected ConfigValue peekPath(Path path, SubstitutionResolver resolver,
protected AbstractConfigValue peekPath(Path path, SubstitutionResolver resolver,
int depth, ConfigResolveOptions 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,
ConfigResolveOptions options) {
String key = path.first();
Path next = path.remainder();
if (next == null) {
ConfigValue v = self.peek(key, resolver, depth, options);
AbstractConfigValue v = self.peek(key, resolver, depth, options);
return v;
} else {
// it's important to ONLY resolve substitutions here, not
@ -131,6 +140,7 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
if (ignoresFallbacks())
throw new ConfigException.BugOrBroken("should not be reached");
boolean changed = false;
boolean allResolved = true;
Map<String, AbstractConfigValue> merged = new HashMap<String, AbstractConfigValue>();
Set<String> allKeys = new HashSet<String>();
@ -146,12 +156,26 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
kept = first;
else
kept = first.withFallback(second);
merged.put(key, kept);
if (first != kept)
changed = true;
if (kept.resolveStatus() == ResolveStatus.UNRESOLVED)
allResolved = false;
}
return new SimpleConfigObject(mergeOrigins(this, fallback), merged,
ResolveStatus.fromBoolean(allResolved), fallback.ignoresFallbacks());
ResolveStatus newResolveStatus = ResolveStatus.fromBoolean(allResolved);
boolean newIgnoresFallbacks = fallback.ignoresFallbacks();
if (changed)
return new SimpleConfigObject(mergeOrigins(this, fallback), merged, newResolveStatus,
newIgnoresFallbacks);
else if (newResolveStatus != resolveStatus() || newIgnoresFallbacks != ignoresFallbacks())
return newCopy(newResolveStatus, newIgnoresFallbacks);
else
return this;
}
@Override
@ -164,18 +188,13 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
if (stack.isEmpty())
throw new ConfigException.BugOrBroken(
"can't merge origins on empty list");
final String prefix = "merge of ";
StringBuilder sb = new StringBuilder();
List<ConfigOrigin> origins = new ArrayList<ConfigOrigin>();
ConfigOrigin firstOrigin = null;
int numMerged = 0;
for (AbstractConfigValue v : stack) {
if (firstOrigin == null)
firstOrigin = v.origin();
String desc = v.origin().description();
if (desc.startsWith(prefix))
desc = desc.substring(prefix.length());
if (v instanceof AbstractConfigObject
&& ((AbstractConfigObject) v).resolveStatus() == ResolveStatus.RESOLVED
&& ((ConfigObject) v).isEmpty()) {
@ -183,22 +202,17 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
// config in the description, since they are
// likely to be "implementation details"
} else {
sb.append(desc);
sb.append(",");
origins.add(v.origin());
numMerged += 1;
}
}
if (numMerged > 0) {
sb.setLength(sb.length() - 1); // chop comma
if (numMerged > 1) {
return new SimpleConfigOrigin(prefix + sb.toString());
} else {
return new SimpleConfigOrigin(sb.toString());
}
} else {
// the configs were all empty.
return firstOrigin;
if (numMerged == 0) {
// the configs were all empty, so just use the first one
origins.add(firstOrigin);
}
return SimpleConfigOrigin.mergeOrigins(origins);
}
static ConfigOrigin mergeOrigins(AbstractConfigObject... stack) {
@ -210,6 +224,8 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
Map<String, AbstractConfigValue> changes = null;
for (String k : keySet()) {
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);
if (modified != v) {
if (changes == null)
@ -223,7 +239,12 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
Map<String, AbstractConfigValue> modified = new HashMap<String, AbstractConfigValue>();
for (String k : keySet()) {
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 {
modified.put(k, peek(k));
}
@ -271,20 +292,36 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(valueType().name());
sb.append("(");
for (String k : keySet()) {
sb.append(k);
sb.append("->");
sb.append(peek(k).toString());
sb.append(",");
protected void render(StringBuilder sb, int indent, boolean formatted) {
if (isEmpty()) {
sb.append("{}");
} else {
sb.append("{");
if (formatted)
sb.append('\n');
for (String k : keySet()) {
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,

View file

@ -16,7 +16,7 @@ import com.typesafe.config.ConfigValue;
* improperly-factored and non-modular code. Please don't add parent().
*
*/
abstract class AbstractConfigValue implements ConfigValue {
abstract class AbstractConfigValue implements ConfigValue, MergeableValue {
final private ConfigOrigin origin;
@ -72,7 +72,7 @@ abstract class AbstractConfigValue implements ConfigValue {
}
@Override
public AbstractConfigValue toValue() {
public AbstractConfigValue toFallbackValue() {
return this;
}
@ -110,7 +110,7 @@ abstract class AbstractConfigValue implements ConfigValue {
if (ignoresFallbacks()) {
return this;
} else {
ConfigValue other = mergeable.toValue();
ConfigValue other = ((MergeableValue) mergeable).toFallbackValue();
if (other instanceof Unmergeable) {
return mergedWithTheUnmergeable((Unmergeable) other);
@ -162,8 +162,39 @@ abstract class AbstractConfigValue implements ConfigValue {
}
@Override
public String toString() {
return valueType().name() + "(" + unwrapped() + ")";
public final String toString() {
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

View file

@ -5,6 +5,7 @@ package com.typesafe.config.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import com.typesafe.config.ConfigException;
@ -76,10 +77,12 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements
AbstractConfigValue merged = null;
for (AbstractConfigValue v : stack) {
AbstractConfigValue resolved = resolver.resolve(v, depth, options);
if (merged == null)
merged = resolved;
else
merged = merged.withFallback(resolved);
if (resolved != null) {
if (merged == null)
merged = resolved;
else
merged = merged.withFallback(resolved);
}
}
return merged;
@ -160,16 +163,58 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("DELAYED_MERGE");
sb.append("(");
for (Object s : stack) {
sb.append(s.toString());
sb.append(",");
protected void render(StringBuilder sb, int indent, String atKey, boolean formatted) {
render(stack, sb, indent, atKey, formatted);
}
// static method also used by ConfigDelayedMergeObject.
static void render(List<AbstractConfigValue> stack, StringBuilder sb, int indent, String atKey,
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
public String toString() {
StringBuilder sb = new StringBuilder();
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();
protected void render(StringBuilder sb, int indent, String atKey, boolean formatted) {
ConfigDelayedMerge.render(stack, sb, indent, atKey, formatted);
}
private static ConfigException notResolved() {

View file

@ -19,7 +19,6 @@ import com.typesafe.config.ConfigObject;
import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigParseOptions;
import com.typesafe.config.ConfigParseable;
import com.typesafe.config.ConfigRoot;
import com.typesafe.config.ConfigSyntax;
import com.typesafe.config.ConfigValue;
@ -45,8 +44,7 @@ public class ConfigImpl {
obj = p.parse(p.options().setAllowMissing(
options.getAllowMissing()));
} else {
obj = SimpleConfigObject.emptyMissing(new SimpleConfigOrigin(
name));
obj = SimpleConfigObject.emptyMissing(SimpleConfigOrigin.newSimple(name));
}
} else {
ConfigParseable confHandle = source.nameToParseable(name + ".conf");
@ -56,13 +54,13 @@ public class ConfigImpl {
if (!options.getAllowMissing() && confHandle == 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");
}
ConfigSyntax syntax = options.getSyntax();
obj = SimpleConfigObject.empty(new SimpleConfigOrigin(name));
obj = SimpleConfigObject.empty(SimpleConfigOrigin.newSimple(name));
if (confHandle != null
&& (syntax == null || syntax == ConfigSyntax.CONF)) {
obj = confHandle.parse(confHandle.options()
@ -89,39 +87,13 @@ public class ConfigImpl {
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 */
public static ConfigObject parseResourcesForPath(String expression,
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,
public static ConfigObject parseResourcesAnySyntax(final Class<?> klass,
String resourceBasename, final ConfigParseOptions baseOptions) {
NameSource source = new NameSource() {
@Override
public ConfigParseable nameToParseable(String name) {
return Parseable.newResource(klass, name, baseOptions);
return Parseable.newResources(klass, name, baseOptions);
}
};
return fromBasename(source, resourceBasename, baseOptions);
@ -139,16 +111,9 @@ public class ConfigImpl {
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) {
ConfigOrigin origin = originDescription != null ? new SimpleConfigOrigin(
originDescription) : null;
ConfigOrigin origin = originDescription != null ? SimpleConfigOrigin
.newSimple(originDescription) : null;
return emptyObject(origin);
}
@ -162,8 +127,8 @@ public class ConfigImpl {
}
// default origin for values created with fromAnyRef and no origin specified
final private static ConfigOrigin defaultValueOrigin = new SimpleConfigOrigin(
"hardcoded value");
final private static ConfigOrigin defaultValueOrigin = SimpleConfigOrigin
.newSimple("hardcoded value");
final private static ConfigBoolean defaultTrueValue = new ConfigBoolean(
defaultValueOrigin, true);
final private static ConfigBoolean defaultFalseValue = new ConfigBoolean(
@ -196,7 +161,7 @@ public class ConfigImpl {
if (originDescription == null)
return defaultValueOrigin;
else
return new SimpleConfigOrigin(originDescription);
return SimpleConfigOrigin.newSimple(originDescription);
}
/** 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 ConfigIncluder fallback;
@ -346,29 +300,34 @@ public class ConfigImpl {
}
}
private static ConfigIncluder defaultIncluder = null;
synchronized static ConfigIncluder defaultIncluder() {
if (defaultIncluder == null) {
defaultIncluder = new SimpleIncluder(null);
}
return defaultIncluder;
private static class DefaultIncluderHolder {
static final ConfigIncluder defaultIncluder = new SimpleIncluder(null);
}
private static AbstractConfigObject systemProperties = null;
synchronized static AbstractConfigObject systemPropertiesAsConfigObject() {
if (systemProperties == null) {
systemProperties = loadSystemProperties();
static ConfigIncluder defaultIncluder() {
try {
return DefaultIncluderHolder.defaultIncluder;
} catch (ExceptionInInitializerError e) {
throw ConfigUtil.extractInitializerError(e);
}
return systemProperties;
}
private static AbstractConfigObject loadSystemProperties() {
return (AbstractConfigObject) Parseable.newProperties(
System.getProperties(),
ConfigParseOptions.defaults().setOriginDescription(
"system properties")).parse();
return (AbstractConfigObject) Parseable.newProperties(System.getProperties(),
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() {
try {
return SystemPropertiesHolder.systemProperties;
} catch (ExceptionInInitializerError e) {
throw ConfigUtil.extractInitializerError(e);
}
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
@ -376,18 +335,10 @@ public class ConfigImpl {
return systemPropertiesAsConfigObject().toConfig();
}
// this is a hack to let us set system props in the test suite
synchronized static void dropSystemPropertiesConfig() {
systemProperties = null;
}
private static AbstractConfigObject envVariables = null;
synchronized static AbstractConfigObject envVariablesAsConfigObject() {
if (envVariables == null) {
envVariables = loadEnvVariables();
}
return envVariables;
// this is a hack to let us set system props in the test suite.
// obviously not thread-safe.
static void reloadSystemPropertiesConfig() {
SystemPropertiesHolder.systemProperties = loadSystemProperties();
}
private static AbstractConfigObject loadEnvVariables() {
@ -395,15 +346,45 @@ public class ConfigImpl {
Map<String, AbstractConfigValue> m = new HashMap<String, AbstractConfigValue>();
for (Map.Entry<String, String> entry : env.entrySet()) {
String key = entry.getKey();
m.put(key, new ConfigString(
new SimpleConfigOrigin("env var " + key), entry.getValue()));
m.put(key,
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 */);
}
private static class EnvVariablesHolder {
static final AbstractConfigObject envVariables = loadEnvVariables();
}
static AbstractConfigObject envVariablesAsConfigObject() {
try {
return EnvVariablesHolder.envVariables;
} catch (ExceptionInInitializerError e) {
throw ConfigUtil.extractInitializerError(e);
}
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static Config envVariablesAsConfig() {
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() {
try {
return ReferenceHolder.referenceConfig;
} catch (ExceptionInInitializerError e) {
throw ConfigUtil.extractInitializerError(e);
}
}
}

View file

@ -34,4 +34,9 @@ final class ConfigNull extends AbstractConfigValue {
String transformToString() {
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() {
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
Unmergeable {
// this is a list of String and Path where the Path
// have to be resolved to values, then if there's more
// this is a list of String and SubstitutionExpression where the
// SubstitutionExpression has to be resolved to values, then if there's more
// than one piece everything is stringified and concatenated
final private List<Object> pieces;
// the length of any prefixes added with relativized()
@ -40,19 +40,24 @@ final class ConfigSubstitution extends AbstractConfigValue implements
this.pieces = pieces;
this.prefixLength = prefixLength;
this.ignoresFallbacks = ignoresFallbacks;
for (Object p : pieces) {
if (p instanceof Path)
throw new RuntimeException("broken here");
}
}
@Override
public ConfigValueType valueType() {
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);
}
@Override
public Object unwrapped() {
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
@ -124,28 +129,27 @@ final class ConfigSubstitution extends AbstractConfigValue implements
return result;
}
private ConfigValue resolve(SubstitutionResolver resolver, Path subst,
private ConfigValue resolve(SubstitutionResolver resolver, SubstitutionExpression subst,
int depth, ConfigResolveOptions options) {
ConfigValue result = findInObject(resolver.root(), resolver, subst,
// First we look up the full path, which means relative to the
// included file if we were not a root file
ConfigValue result = findInObject(resolver.root(), resolver, subst.path(),
depth, options);
// when looking up system props and env variables,
// we don't want the prefix that was added when
// we were included in another file.
Path unprefixed = subst.subPath(prefixLength);
if (result == null && options.getUseSystemProperties()) {
result = findInObject(ConfigImpl.systemPropertiesAsConfigObject(), null,
unprefixed, depth, options);
}
if (result == null && options.getUseSystemEnvironment()) {
result = findInObject(ConfigImpl.envVariablesAsConfigObject(), null,
unprefixed, depth, options);
}
if (result == null) {
result = new ConfigNull(origin());
// Then we want to check relative to the root file. We don't
// want the prefix we were included at to be used when looking up
// env variables either.
Path unprefixed = subst.path().subPath(prefixLength);
if (result == null && prefixLength > 0) {
result = findInObject(resolver.root(), resolver, unprefixed, depth, options);
}
if (result == null && options.getUseSystemEnvironment()) {
result = findInObject(ConfigImpl.envVariablesAsConfigObject(), null, unprefixed,
depth, options);
}
}
return result;
@ -160,28 +164,40 @@ final class ConfigSubstitution extends AbstractConfigValue implements
if (p instanceof String) {
sb.append((String) p);
} else {
ConfigValue v = resolve(resolver, (Path) p, depth, options);
switch (v.valueType()) {
case NULL:
// nothing; becomes empty string
break;
case LIST:
case OBJECT:
// cannot substitute lists and objects into strings
throw new ConfigException.WrongType(v.origin(),
((Path) p).render(),
"not a list or object", v.valueType().name());
default:
sb.append(((AbstractConfigValue) v).transformToString());
SubstitutionExpression exp = (SubstitutionExpression) p;
ConfigValue v = resolve(resolver, exp, depth, options);
if (v == null) {
if (exp.optional()) {
// append nothing to StringBuilder
} else {
throw new ConfigException.UnresolvedSubstitution(origin(),
exp.toString());
}
} else {
switch (v.valueType()) {
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());
} else {
if (!(pieces.get(0) instanceof Path))
if (!(pieces.get(0) instanceof SubstitutionExpression))
throw new ConfigException.BugOrBroken(
"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 +226,10 @@ final class ConfigSubstitution extends AbstractConfigValue implements
ConfigSubstitution relativized(Path prefix) {
List<Object> newPieces = new ArrayList<Object>();
for (Object p : pieces) {
if (p instanceof Path) {
newPieces.add(((Path) p).prepend(prefix));
if (p instanceof SubstitutionExpression) {
SubstitutionExpression exp = (SubstitutionExpression) p;
newPieces.add(exp.changePath(exp.path().prepend(prefix)));
} else {
newPieces.add(p);
}
@ -243,16 +261,13 @@ final class ConfigSubstitution extends AbstractConfigValue implements
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("SUBST");
sb.append("(");
protected void render(StringBuilder sb, int indent, boolean formatted) {
for (Object p : pieces) {
sb.append(p.toString());
sb.append(",");
if (p instanceof SubstitutionExpression) {
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

@ -3,6 +3,12 @@
*/
package com.typesafe.config.impl;
import java.io.File;
import java.net.URISyntaxException;
import java.net.URL;
import com.typesafe.config.ConfigException;
/** This is public just for the "config" package to use, don't touch it */
final public class ConfigUtil {
@ -118,4 +124,26 @@ final public class ConfigUtil {
}
return s.substring(start, end);
}
/** This is public just for the "config" package to use, don't touch it! */
public static ConfigException extractInitializerError(ExceptionInInitializerError e) {
Throwable cause = e.getCause();
if (cause != null && cause instanceof ConfigException) {
return (ConfigException) cause;
} else {
throw e;
}
}
static File urlToFile(URL url) {
// this isn't really right, clearly, but not sure what to do.
try {
// this will properly handle hex escapes, etc.
return new File(url.toURI());
} catch (URISyntaxException e) {
// this handles some stuff like file:///c:/Whatever/
// apparently but mangles handling of hex escapes
return new File(url.getPath());
}
}
}

View file

@ -0,0 +1,9 @@
package com.typesafe.config.impl;
import com.typesafe.config.ConfigMergeable;
import com.typesafe.config.ConfigValue;
interface MergeableValue extends ConfigMergeable {
// converts a Config to its root object and a ConfigValue to itself
ConfigValue toFallbackValue();
}

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.URISyntaxException;
import java.net.URL;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Properties;
@ -39,6 +40,7 @@ import com.typesafe.config.ConfigValue;
public abstract class Parseable implements ConfigParseable {
private ConfigIncludeContext includeContext;
private ConfigParseOptions initialOptions;
private ConfigOrigin initialOrigin;
protected Parseable() {
@ -54,9 +56,6 @@ public abstract class Parseable implements ConfigParseable {
}
ConfigParseOptions modified = baseOptions.setSyntax(syntax);
if (modified.getOriginDescription() == null)
modified = modified.setOriginDescription(originDescription());
modified = modified.appendIncluder(ConfigImpl.defaultIncluder());
return modified;
@ -71,6 +70,11 @@ public abstract class Parseable implements ConfigParseable {
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
@ -108,33 +112,26 @@ public abstract class Parseable implements ConfigParseable {
return forceParsedToObject(parseValue(baseOptions));
}
AbstractConfigValue parseValue(ConfigParseOptions baseOptions) {
// note that we are NOT using our "options" and "origin" fields,
final AbstractConfigValue parseValue(ConfigParseOptions baseOptions) {
// note that we are NOT using our "initialOptions",
// but using the ones from the passed-in options. The idea is that
// callers can get our original options and then parse with different
// ones if they want.
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);
}
protected AbstractConfigValue parseValue(ConfigOrigin origin,
final private AbstractConfigValue parseValue(ConfigOrigin origin,
ConfigParseOptions finalOptions) {
try {
Reader reader = reader();
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();
}
return rawParseValue(origin, finalOptions);
} catch (IOException e) {
if (finalOptions.getAllowMissing()) {
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() {
return forceParsedToObject(parseValue(options()));
}
@ -152,13 +171,13 @@ public abstract class Parseable implements ConfigParseable {
return parseValue(options());
}
abstract String originDescription();
@Override
public URL url() {
return null;
public final ConfigOrigin origin() {
return initialOrigin;
}
protected abstract ConfigOrigin createOrigin();
@Override
public ConfigParseOptions options() {
return initialOptions;
@ -228,32 +247,18 @@ public abstract class Parseable implements ConfigParseable {
}
}
private final static class ParseableInputStream extends Parseable {
final private InputStream input;
static File relativeTo(File file, String filename) {
File child = new File(filename);
ParseableInputStream(InputStream input, ConfigParseOptions options) {
this.input = input;
postConstruct(options);
}
if (child.isAbsolute())
return null;
@Override
protected Reader reader() {
return doNotClose(readerFromStream(input));
}
File parent = file.getParentFile();
@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);
if (parent == null)
return null;
else
return new File(parent, filename);
}
private final static class ParseableReader extends Parseable {
@ -270,8 +275,8 @@ public abstract class Parseable implements ConfigParseable {
}
@Override
String originDescription() {
return "Reader";
protected ConfigOrigin createOrigin() {
return SimpleConfigOrigin.newSimple("Reader");
}
}
@ -297,8 +302,8 @@ public abstract class Parseable implements ConfigParseable {
}
@Override
String originDescription() {
return "String";
protected ConfigOrigin createOrigin() {
return SimpleConfigOrigin.newSimple("String");
}
}
@ -335,13 +340,8 @@ public abstract class Parseable implements ConfigParseable {
}
@Override
String originDescription() {
return input.toExternalForm();
}
@Override
public URL url() {
return input;
protected ConfigOrigin createOrigin() {
return SimpleConfigOrigin.newURL(input);
}
@Override
@ -352,7 +352,13 @@ public abstract class Parseable implements ConfigParseable {
}
public static Parseable newURL(URL input, ConfigParseOptions options) {
return new ParseableURL(input, options);
// we want file: URLs and files to always behave the same, so switch
// to a file if it's a file: URL
if (input.getProtocol().equals("file")) {
return newFile(ConfigUtil.urlToFile(input), options);
} else {
return new ParseableURL(input, options);
}
}
private final static class ParseableFile extends Parseable {
@ -376,28 +382,33 @@ public abstract class Parseable implements ConfigParseable {
@Override
ConfigParseable relativeTo(String filename) {
try {
URL url = relativeTo(input.toURI().toURL(), filename);
if (url == null)
return null;
return newURL(url, options().setOriginDescription(null));
} catch (MalformedURLException e) {
File sibling;
if ((new File(filename)).isAbsolute()) {
sibling = new File(filename);
} else {
// this may return null
sibling = relativeTo(input, filename);
}
if (sibling == null)
return null;
if (sibling.exists()) {
return newFile(sibling, options().setOriginDescription(null));
} else {
// fall back to classpath; we treat the "filename" as absolute
// (don't add a package name in front),
// if it starts with "/" then remove the "/", for consistency
// with ParseableResources.relativeTo
String resource = filename;
if (filename.startsWith("/"))
resource = filename.substring(1);
return newResources(this.getClass().getClassLoader(), resource, options()
.setOriginDescription(null));
}
}
@Override
String originDescription() {
return input.getPath();
}
@Override
public URL url() {
try {
return input.toURI().toURL();
} catch (MalformedURLException e) {
return null;
}
protected ConfigOrigin createOrigin() {
return SimpleConfigOrigin.newFile(input.getPath());
}
@Override
@ -410,25 +421,61 @@ public abstract class Parseable implements ConfigParseable {
return new ParseableFile(input, options);
}
private final static class ParseableResource extends Parseable {
final private Class<?> klass;
private final static class ParseableResources extends Parseable {
final private ClassLoader loader;
final private String resource;
ParseableResource(Class<?> klass, String resource,
ParseableResources(ClassLoader loader, String resource,
ConfigParseOptions options) {
this.klass = klass;
this.loader = loader;
this.resource = resource;
postConstruct(options);
}
@Override
protected Reader reader() throws IOException {
InputStream stream = klass.getResourceAsStream(resource);
if (stream == null) {
throw new IOException("resource not found on classpath: "
+ resource);
throw new ConfigException.BugOrBroken(
"reader() should not be called on resources");
}
@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
@ -436,48 +483,86 @@ public abstract class Parseable implements ConfigParseable {
return syntaxFromExtension(resource);
}
@Override
ConfigParseable relativeTo(String filename) {
// not using File.isAbsolute because resource paths always use '/'
// (?)
if (filename.startsWith("/"))
static String parent(String resource) {
// the "resource" is not supposed to begin with a "/"
// because it's supposed to be the raw resource
// (ClassLoader#getResource), not the
// resource "syntax" (Class#getResource)
int i = resource.lastIndexOf('/');
if (i < 0) {
return null;
} else {
return resource.substring(0, i);
}
}
// here we want to build a new resource name and let
// the class loader have it, rather than getting the
// url with getResource() and relativizing to that url.
// This is needed in case the class loader is going to
// search a classpath.
File parent = new File(resource).getParentFile();
if (parent == null)
return newResource(klass, "/" + filename, options()
.setOriginDescription(null));
else
return newResource(klass, new File(parent, filename).getPath(),
@Override
ConfigParseable relativeTo(String sibling) {
if (sibling.startsWith("/")) {
// if it starts with "/" then don't make it relative to
// the including resource
return newResources(loader, sibling.substring(1),
options().setOriginDescription(null));
} else {
// here we want to build a new resource name and let
// the class loader have it, rather than getting the
// url with getResource() and relativizing to that url.
// This is needed in case the class loader is going to
// search a classpath.
String parent = parent(resource);
if (parent == null)
return newResources(loader, sibling, options().setOriginDescription(null));
else
return newResources(loader, parent + "/" + sibling, options()
.setOriginDescription(null));
}
}
@Override
String originDescription() {
return resource + " on classpath";
}
@Override
public URL url() {
return klass.getResource(resource);
protected ConfigOrigin createOrigin() {
return SimpleConfigOrigin.newResource(resource);
}
@Override
public String toString() {
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) {
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 {
@ -495,7 +580,7 @@ public abstract class Parseable implements ConfigParseable {
}
@Override
protected AbstractConfigObject parseValue(ConfigOrigin origin,
protected AbstractConfigObject rawParseValue(ConfigOrigin origin,
ConfigParseOptions finalOptions) {
return PropertiesParser.fromProperties(origin, props);
}
@ -506,13 +591,8 @@ public abstract class Parseable implements ConfigParseable {
}
@Override
String originDescription() {
return "properties";
}
@Override
public URL url() {
return null;
protected ConfigOrigin createOrigin() {
return SimpleConfigOrigin.newSimple("properties");
}
@Override

View file

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

View file

@ -139,11 +139,25 @@ final class Path {
}
// this doesn't have a very precise meaning, just to reduce
// noise from quotes in the rendered path
// noise from quotes in the rendered path for average cases
static boolean hasFunkyChars(String s) {
for (int i = 0; i < s.length(); ++i) {
int length = s.length();
if (length == 0)
return false;
// if the path starts with something that could be a number,
// we need to quote it because the number could be invalid,
// for example it could be a hyphen with no digit afterward
// or the exponent "e" notation could be mangled.
char first = s.charAt(0);
if (!(Character.isLetter(first)))
return true;
for (int i = 1; i < length; ++i) {
char c = s.charAt(i);
if (Character.isLetterOrDigit(c) || c == ' ')
if (Character.isLetterOrDigit(c) || c == '-' || c == '_')
continue;
else
return true;
@ -152,7 +166,7 @@ final class Path {
}
private void appendToStringBuilder(StringBuilder sb) {
if (hasFunkyChars(first))
if (hasFunkyChars(first) || first.isEmpty())
sb.append(ConfigUtil.renderJsonString(first));
else
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;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import com.typesafe.config.Config;
@ -25,16 +27,16 @@ import com.typesafe.config.ConfigValueType;
* key-value pairs would be all the tree's leaf values, in a big flat list with
* their full paths.
*/
class SimpleConfig implements Config {
final class SimpleConfig implements Config, MergeableValue {
AbstractConfigObject object;
final private AbstractConfigObject object;
SimpleConfig(AbstractConfigObject object) {
this.object = object;
}
@Override
public AbstractConfigObject toObject() {
public AbstractConfigObject root() {
return object;
}
@ -43,35 +45,22 @@ class SimpleConfig implements Config {
return object.origin();
}
/**
* Returns a version of this config that implements the ConfigRoot
* interface.
*
* @return a config root
*/
RootConfig asRoot(Path rootPath) {
return asRoot(object, rootPath);
@Override
public SimpleConfig resolve() {
return resolve(ConfigResolveOptions.defaults());
}
// RootConfig overrides this to avoid a new object on unchanged path.
protected RootConfig asRoot(AbstractConfigObject underlying,
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) {
@Override
public SimpleConfig resolve(ConfigResolveOptions options) {
AbstractConfigValue resolved = SubstitutionResolver.resolve(object,
object, options);
return (AbstractConfigObject) resolved;
if (resolved == object)
return this;
else
return new SimpleConfig((AbstractConfigObject) resolved);
}
@Override
public boolean hasPath(String pathExpression) {
Path path = Path.newPath(pathExpression);
@ -196,13 +185,13 @@ class SimpleConfig implements Config {
}
@Override
public Long getMemorySizeInBytes(String path) {
public Long getBytes(String path) {
Long size = null;
try {
size = getLong(path);
} catch (ConfigException.WrongType e) {
ConfigValue v = find(path, ConfigValueType.STRING, path);
size = parseMemorySizeInBytes((String) v.unwrapped(),
size = parseBytes((String) v.unwrapped(),
v.origin(), path);
}
return size;
@ -338,7 +327,7 @@ class SimpleConfig implements Config {
}
@Override
public List<Long> getMemorySizeInBytesList(String path) {
public List<Long> getBytesList(String path) {
List<Long> l = new ArrayList<Long>();
List<? extends ConfigValue> list = getList(path);
for (ConfigValue v : list) {
@ -346,7 +335,7 @@ class SimpleConfig implements Config {
l.add(((Number) v.unwrapped()).longValue());
} else if (v.valueType() == ConfigValueType.STRING) {
String s = (String) v.unwrapped();
Long n = parseMemorySizeInBytes(s, v.origin(), path);
Long n = parseBytes(s, v.origin(), path);
l.add(n);
} else {
throw new ConfigException.WrongType(v.origin(), path,
@ -389,7 +378,7 @@ class SimpleConfig implements Config {
}
@Override
public AbstractConfigObject toValue() {
public AbstractConfigObject toFallbackValue() {
return object;
}
@ -509,23 +498,87 @@ class SimpleConfig implements Config {
}
private static enum MemoryUnit {
BYTES(1), KILOBYTES(1024), MEGABYTES(1024 * 1024), GIGABYTES(
1024 * 1024 * 1024), TERABYTES(1024 * 1024 * 1024 * 1024);
BYTES("", 1024, 0),
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;
}
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
* is assumed to be in bytes. The returned value is in bytes. The purpose of
* this function is to implement the memory-size-related methods in the
* ConfigObject interface. The units parsed are interpreted as powers of
* two, that is, the convention for memory rather than the convention for
* disk space.
* Parses a size-in-bytes string. If no units are specified in the string,
* it is assumed to be in bytes. The returned value is in bytes. The purpose
* of this function is to implement the size-in-bytes-related methods in the
* Config interface.
*
* @param input
* the string to parse
@ -537,19 +590,12 @@ class SimpleConfig implements Config {
* @throws ConfigException
* if string is invalid
*/
public static long parseMemorySizeInBytes(String input,
ConfigOrigin originForException, String pathForException) {
public static long parseBytes(String input, ConfigOrigin originForException,
String pathForException) {
String s = ConfigUtil.unicodeTrim(input);
String unitStringMaybePlural = getUnits(s);
String unitString;
if (unitStringMaybePlural.endsWith("s"))
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()));
String unitString = getUnits(s);
String numberString = ConfigUtil.unicodeTrim(s.substring(0,
s.length() - unitString.length()));
// this would be caught later anyway, but the error message
// is more helpful if we check it here.
@ -558,40 +604,197 @@ class SimpleConfig implements Config {
pathForException, "No number in size-in-bytes value '"
+ input + "'");
MemoryUnit units = null;
MemoryUnit units = MemoryUnit.parseUnit(unitString);
// the short abbreviations are case-insensitive but you can't write the
// long form words in all caps.
if (unitString.equals("") || unitStringLower.equals("b")
|| unitString.equals("byte")) {
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)");
if (units == null) {
throw new ConfigException.BadValue(originForException, pathForException,
"Could not parse size-in-bytes unit '" + unitString
+ "' (try k, K, kB, KiB, kilobytes, kibibytes)");
}
try {
// if the string is purely digits, parse as an integer to avoid
// possible precision loss;
// otherwise as a double.
// possible precision loss; otherwise as a double.
if (numberString.matches("[0-9]+")) {
return Long.parseLong(numberString) * units.bytes;
} else {
return (long) (Double.parseDouble(numberString) * units.bytes);
}
} catch (NumberFormatException e) {
throw new ConfigException.BadValue(originForException,
pathForException, "Could not parse memory size number '"
+ numberString + "'");
throw new ConfigException.BadValue(originForException, pathForException,
"Could not parse size-in-bytes number '" + 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.ListIterator;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigList;
import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigResolveOptions;
@ -68,8 +67,9 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList {
}
// once the new list is created, all elements
// have to go in it.
if (changed != null) {
// have to go in it. if modifyChild returned
// null, we drop that element.
if (changed != null && modified != null) {
changed.add(modified);
}
@ -77,9 +77,6 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList {
}
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);
} else {
return this;
@ -135,18 +132,34 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList {
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(valueType().name());
sb.append("(");
for (ConfigValue e : value) {
sb.append(e.toString());
sb.append(",");
protected void render(StringBuilder sb, int indent, boolean formatted) {
if (value.isEmpty()) {
sb.append("[]");
} else {
sb.append("[");
if (formatted)
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
@ -160,7 +173,7 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList {
}
@Override
public ConfigValue get(int index) {
public AbstractConfigValue get(int 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 SimpleConfigObject emptyInstance = empty(new SimpleConfigOrigin(
EMPTY_NAME));
final private static SimpleConfigObject emptyInstance = empty(SimpleConfigOrigin
.newSimple(EMPTY_NAME));
final static SimpleConfigObject empty() {
return emptyInstance;
@ -128,7 +128,7 @@ final class SimpleConfigObject extends AbstractConfigObject {
}
final static SimpleConfigObject emptyMissing(ConfigOrigin baseOrigin) {
return new SimpleConfigObject(new SimpleConfigOrigin(
return new SimpleConfigObject(SimpleConfigOrigin.newSimple(
baseOrigin.description() + " (not found)"),
Collections.<String, AbstractConfigValue> emptyMap());
}

View file

@ -3,26 +3,101 @@
*/
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;
// 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 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.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
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
public boolean equals(Object other) {
if (other instanceof SimpleConfigOrigin) {
return this.description
.equals(((SimpleConfigOrigin) other).description);
SimpleConfigOrigin otherOrigin = (SimpleConfigOrigin) other;
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 {
return false;
}
@ -30,11 +105,201 @@ final class SimpleConfigOrigin implements ConfigOrigin {
@Override
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
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 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;
SubstitutionResolver(AbstractConfigObject root) {
@ -31,9 +33,11 @@ final class SubstitutionResolver {
} else {
AbstractConfigValue resolved = original.resolveSubstitutions(this,
depth, options);
if (resolved.resolveStatus() != ResolveStatus.RESOLVED)
throw new ConfigException.BugOrBroken(
"resolveSubstitutions() did not give us a resolved object");
if (resolved != null) {
if (resolved.resolveStatus() != ResolveStatus.RESOLVED)
throw new ConfigException.BugOrBroken(
"resolveSubstitutions() did not give us a resolved object");
}
memos.put(original, resolved);
return resolved;
}

View file

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

View file

@ -125,11 +125,13 @@ final class Tokens {
// This is not a Value, because it requires special processing
static private class Substitution extends Token {
final private ConfigOrigin origin;
final private boolean optional;
final private List<Token> value;
Substitution(ConfigOrigin origin, List<Token> expression) {
Substitution(ConfigOrigin origin, boolean optional, List<Token> expression) {
super(TokenType.SUBSTITUTION);
this.origin = origin;
this.optional = optional;
this.value = expression;
}
@ -137,6 +139,10 @@ final class Tokens {
return origin;
}
boolean optional() {
return optional;
}
List<Token> 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 END = new Token(TokenType.END);
final static Token COMMA = new Token(TokenType.COMMA);
@ -255,8 +270,8 @@ final class Tokens {
return new UnquotedText(origin, s);
}
static Token newSubstitution(ConfigOrigin origin, List<Token> expression) {
return new Substitution(origin, expression);
static Token newSubstitution(ConfigOrigin origin, boolean optional, List<Token> expression) {
return new Substitution(origin, optional, expression);
}
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
# this level is used by the configured loggers (see "event-handlers") as soon
# 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?
logConfigOnStart = off # Log the complete configuration at INFO level when the actor system is started.
@ -81,7 +81,7 @@ akka {
# optional
replication { # use replication or not? only makes sense for a stateful actor
# FIXME should we have this config option here? If so, implement it all through.
# serialize-mailbox not implemented, ticket #1412
serialize-mailbox = off # should the actor mailbox be part of the serialized snapshot?
# default is 'off'
@ -125,6 +125,26 @@ akka {
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
}
# 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
ticksPerWheel = 512
}
}

View file

@ -162,25 +162,6 @@ object Actor {
type Receive = PartialFunction[Any, Unit]
/**
* This decorator adds invocation logging to a Receive function.
*/
class LoggingReceive(source: AnyRef, r: Receive)(implicit system: ActorSystem) extends Receive {
def isDefinedAt(o: Any) = {
val handled = r.isDefinedAt(o)
system.eventStream.publish(Debug(LogSource.fromAnyRef(source), "received " + (if (handled) "handled" else "unhandled") + " message " + o))
handled
}
def apply(o: Any): Unit = r(o)
}
object LoggingReceive {
def apply(source: AnyRef, r: Receive)(implicit system: ActorSystem): Receive = r match {
case _: LoggingReceive r
case _ new LoggingReceive(source, r)
}
}
object emptyBehavior extends Receive {
def isDefinedAt(x: Any) = false
def apply(x: Any) = throw new UnsupportedOperationException("empty behavior apply()")
@ -237,22 +218,6 @@ trait Actor {
*/
implicit def defaultTimeout = system.settings.ActorTimeout
/**
* Wrap a Receive partial function in a logging enclosure, which sends a
* debug message to the EventHandler each time before a message is matched.
* This includes messages which are not handled.
*
* <pre><code>
* def receive = loggable {
* case x => ...
* }
* </code></pre>
*
* This method does NOT modify the given Receive unless
* akka.actor.debug.receive is set within akka.conf.
*/
def loggable(self: AnyRef)(r: Receive): Receive = if (system.settings.AddLoggingReceive) LoggingReceive(self, r) else r //TODO FIXME Shouldn't this be in a Loggable-trait?
/**
* The 'self' field holds the ActorRef for this actor.
* <p/>
@ -365,24 +330,24 @@ trait Actor {
* 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
*/
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.
*/
def unbecome() { context.unbecome() }
final def unbecome() { context.unbecome() }
/**
* Registers this actor as a Monitor for 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
* @return the provided ActorRef
*/
def unwatch(subject: ActorRef): ActorRef = self stopsWatching subject
final def unwatch(subject: ActorRef): ActorRef = context stopsWatching subject
// =========================================
// ==== INTERNAL IMPLEMENTATION DETAILS ====
@ -397,6 +362,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 parent: ActorRef
def startsWatching(subject: ActorRef): ActorRef
def stopsWatching(subject: ActorRef): ActorRef
}
private[akka] object ActorCell {
@ -148,13 +152,13 @@ private[akka] class ActorCell(
// ➡➡➡ NEVER SEND THE SAME SYSTEM MESSAGE OBJECT TO TWO ACTORS ⬅⬅⬅
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 ⬅⬅⬅
dispatcher.systemDispatch(this, Link(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 ⬅⬅⬅
dispatcher.systemDispatch(this, Unlink(subject))
subject
@ -207,6 +211,7 @@ private[akka] class ActorCell(
checkReceiveTimeout
if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.path.toString, "started (" + actor + ")"))
} catch {
// FIXME catching all and continue isn't good for OOME, ticket #1418
case e
try {
system.eventStream.publish(Error(e, self.path.toString, "error while creating actor"))
@ -239,6 +244,7 @@ private[akka] class ActorCell(
props.faultHandler.handleSupervisorRestarted(cause, self, children)
} catch {
// FIXME catching all and continue isn't good for OOME, ticket #1418
case e try {
system.eventStream.publish(Error(e, self.path.toString, "error while creating actor"))
// prevent any further messages to be processed until the actor has been restarted
@ -300,7 +306,7 @@ private[akka] class ActorCell(
} catch {
case e //Should we really catch everything here?
system.eventStream.publish(Error(e, self.path.toString, "error while processing " + message))
//TODO FIXME How should problems here be handled?
//TODO FIXME How should problems here be handled???
throw e
}
}
@ -311,7 +317,7 @@ private[akka] class ActorCell(
currentMessage = messageHandle
try {
try {
cancelReceiveTimeout() // FIXME: leave this here?
cancelReceiveTimeout() // FIXME: leave this here???
messageHandle.message match {
case msg: AutoReceivedMessage autoReceiveMessage(messageHandle)
case msg if stopping // receiving Terminated in response to stopping children is too common to generate noise

View file

@ -48,7 +48,7 @@ import akka.event.DeathWatch
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
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
/**
@ -100,16 +100,6 @@ abstract class ActorRef extends java.lang.Comparable[ActorRef] with Serializable
*/
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
/**
* Resumes a suspended actor.
*/
def resume(): Unit //TODO FIXME REMOVE THIS
/**
* Shuts down the actor its dispatcher and message queue.
*/
@ -120,24 +110,6 @@ abstract class ActorRef extends java.lang.Comparable[ActorRef] with Serializable
*/
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
/**
* 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
// FIXME check if we should scramble the bits or whether they can stay the same
override def hashCode: Int = path.hashCode
@ -162,7 +134,7 @@ class LocalActorRef private[akka] (
val systemService: Boolean = false,
_receiveTimeout: Option[Long] = None,
_hotswap: Stack[PartialFunction[Any, Unit]] = Props.noHotSwap)
extends ActorRef with ScalaActorRef {
extends ActorRef with ScalaActorRef with RefInternals {
/*
* actorCell.start() publishes actorCell & this to the dispatcher, which
@ -190,13 +162,13 @@ class LocalActorRef private[akka] (
* message sends done from the same thread after calling this method will not
* be processed until resumed.
*/
//FIXME TODO REMOVE THIS, NO REPLACEMENT
//FIXME TODO REMOVE THIS, NO REPLACEMENT, ticket #1415
def suspend(): Unit = actorCell.suspend()
/**
* Resumes a suspended actor.
*/
//FIXME TODO REMOVE THIS, NO REPLACEMENT
//FIXME TODO REMOVE THIS, NO REPLACEMENT, ticket #1415
def resume(): Unit = actorCell.resume()
/**
@ -204,29 +176,11 @@ class LocalActorRef private[akka] (
*/
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 =========
protected[akka] def underlying: ActorCell = actorCell
// FIXME TODO: remove this method
// FIXME TODO: remove this method. It is used in testkit.
// @deprecated("This method does a spin-lock to block for the actor, which might never be there, do not use this", "2.0")
protected[akka] def underlyingActorInstance: Actor = {
var instance = actorCell.actor
@ -285,7 +239,11 @@ trait ScalaActorRef { ref: ActorRef ⇒
* implicit 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
}
@ -308,11 +266,10 @@ case class SerializedActorRef(path: String) {
/**
* Trait for ActorRef implementations where all methods contain default stubs.
*/
trait MinimalActorRef extends ActorRef with ScalaActorRef {
def startsWatching(actorRef: ActorRef): ActorRef = actorRef
def stopsWatching(actorRef: ActorRef): ActorRef = actorRef
trait MinimalActorRef extends ActorRef with ScalaActorRef with RefInternals {
//FIXME REMOVE THIS, ticket #1416
//FIXME REMOVE THIS, ticket #1415
def suspend(): Unit = ()
def resume(): Unit = ()

View file

@ -20,6 +20,7 @@ import org.jboss.netty.akka.util.internal.ConcurrentIdentityHashMap
import akka.event._
import akka.event.Logging.Error._
import akka.event.Logging.Warning
import java.io.Closeable
/**
* Interface for all ActorRef providers to implement.
@ -41,10 +42,10 @@ trait ActorRefProvider {
*/
def deathWatch: DeathWatch
// FIXME: remove/replace?
// FIXME: remove/replace???
def nodename: String
// FIXME: remove/replace?
// FIXME: remove/replace???
def clustername: String
/**
@ -76,8 +77,14 @@ trait ActorRefProvider {
def actorOf(system: ActorSystemImpl, props: Props, supervisor: ActorRef, name: String, systemService: Boolean = false): ActorRef
/**
* <<<<<<< HEAD
* Create actor reference for a specified local or remote path. If no such
* actor exists, it will be (equivalent to) a dead letter reference.
* =======
* Create an Actor with the given full path below the given supervisor.
*
* FIXME: Remove! this is dangerous!?
* >>>>>>> master
*/
def actorFor(path: ActorPath): ActorRef
@ -400,7 +407,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 with Closeable {
def schedule(receiver: ActorRef, message: Any, initialDelay: Duration, delay: Duration): Cancellable =
new DefaultCancellable(hashedWheelTimer.newTimeout(createContinuousTask(receiver, message, delay), initialDelay))
@ -420,8 +432,7 @@ class DefaultScheduler(hashedWheelTimer: HashedWheelTimer, system: ActorSystem)
private def createSingleTask(runnable: Runnable): TimerTask =
new TimerTask() {
def run(timeout: org.jboss.netty.akka.util.Timeout) {
// FIXME: consider executing runnable inside main dispatcher to prevent blocking of scheduler
runnable.run()
dispatcher.dispatchTask(() runnable.run())
}
}
@ -435,7 +446,7 @@ class DefaultScheduler(hashedWheelTimer: HashedWheelTimer, system: ActorSystem)
private def createSingleTask(f: () Unit): TimerTask =
new TimerTask {
def run(timeout: org.jboss.netty.akka.util.Timeout) {
f()
dispatcher.dispatchTask(f)
}
}
@ -447,7 +458,7 @@ class DefaultScheduler(hashedWheelTimer: HashedWheelTimer, system: ActorSystem)
receiver ! message
timeout.getTimer.newTimeout(this, delay)
} 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.")
}
}
}
@ -456,13 +467,13 @@ class DefaultScheduler(hashedWheelTimer: HashedWheelTimer, system: ActorSystem)
private def createContinuousTask(f: () Unit, delay: Duration): TimerTask = {
new TimerTask {
def run(timeout: org.jboss.netty.akka.util.Timeout) {
f()
dispatcher.dispatchTask(f)
timeout.getTimer.newTimeout(this, delay)
}
}
}
private[akka] def stop() = hashedWheelTimer.stop()
def close() = hashedWheelTimer.stop()
}
class DefaultCancellable(val timeout: org.jboss.netty.akka.util.Timeout) extends Cancellable {

View file

@ -13,15 +13,17 @@ import java.util.concurrent.TimeUnit.MILLISECONDS
import java.util.concurrent.TimeUnit.NANOSECONDS
import java.io.File
import com.typesafe.config.Config
import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.ConfigRoot
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 akka.util.{ Helpers, Duration, ReflectiveAccess }
import java.util.concurrent.atomic.AtomicLong
import java.util.concurrent.{ CountDownLatch, Executors, ConcurrentHashMap }
import scala.annotation.tailrec
import org.jboss.netty.akka.util.internal.ConcurrentIdentityHashMap
import java.io.Closeable
object ActorSystem {
@ -42,17 +44,32 @@ object ActorSystem {
def create(name: String, config: Config): ActorSystem = apply(name, config)
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 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 apply(): ActorSystem = apply("default")
class Settings(cfg: Config, val name: String) {
private def referenceConfig: Config =
ConfigFactory.parseResource(classOf[ActorSystem], "/akka-actor-reference.conf",
ConfigParseOptions.defaults.setAllowMissing(false))
val config: ConfigRoot = ConfigFactory.emptyRoot("akka-actor").withFallback(cfg).withFallback(referenceConfig).resolve()
// Verify that the Config is sane and has our reference config.
val config: Config =
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 config._
@ -95,15 +112,17 @@ object ActorSystem {
throw new ConfigurationException("Akka JAR version [" + Version +
"] does not match the provided config version [" + ConfigVersion + "]")
override def toString: String = {
config.toString
}
override def toString: String = config.root.render
}
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
val defaultLocation: String = (systemMode orElse envMode).map("akka." + _).getOrElse("akka")
@ -129,7 +148,7 @@ object ActorSystem {
private def fromClasspath = try {
Option(ConfigFactory.systemProperties.withFallback(
ConfigFactory.parseResourceAnySyntax(ActorSystem.getClass, "/" + defaultLocation, configParseOptions)))
ConfigFactory.parseResourcesAnySyntax(ActorSystem.getClass, "/" + defaultLocation, configParseOptions)))
} catch { case _ None }
private def fromHome = try {
@ -208,8 +227,6 @@ abstract class ActorSystem extends ActorRefFactory {
* effort basis and hence not strictly guaranteed.
*/
def deadLetters: ActorRef
// FIXME: do not publish this
def deadLetterMailbox: Mailbox
/**
* Light-weight scheduler for running asynchronous tasks after some deadline
@ -234,7 +251,7 @@ abstract class ActorSystem extends ActorRefFactory {
* Register a block of code to run after all actors in this actor system have
* 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
@ -272,7 +289,7 @@ abstract class ActorSystem extends ActorRefFactory {
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 {
if (!name.matches("""^\w+$"""))
throw new IllegalArgumentException("invalid ActorSystem name '" + name + "', must contain only word characters (i.e. [a-zA-Z_0-9])")
@ -314,7 +331,7 @@ class ActorSystemImpl(val name: String, val applicationConfig: Config) extends A
eventStream.startStdoutLogger(settings)
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 deadLetters = new DeadLetterActorRef(eventStream)
val deadLetterMailbox = new Mailbox(null) {
@ -361,9 +378,9 @@ class ActorSystemImpl(val name: String, val applicationConfig: Config) extends A
}
val dispatcherFactory = new Dispatchers(settings, DefaultDispatcherPrerequisites(eventStream, deadLetterMailbox, scheduler))
// TODO why implicit val dispatcher?
implicit val dispatcher = dispatcherFactory.defaultGlobalDispatcher
//FIXME Set this to a Failure when things bubble to the top
def terminationFuture: Future[Unit] = provider.terminationFuture
def guardian: ActorRef = provider.guardian
def systemGuardian: ActorRef = provider.systemGuardian
@ -389,14 +406,42 @@ class ActorSystemImpl(val name: String, val applicationConfig: Config) extends A
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) }
// TODO shutdown all that other stuff, whatever that may be
def stop() {
guardian.stop()
terminationFuture onComplete (_ scheduler.stop())
terminationFuture onComplete (_ dispatcher.shutdown())
try terminationFuture.await(10 seconds) catch {
case _: FutureTimeoutException log.warning("Failed to stop [{}] within 10 seconds", name)
}
// Dispatchers shutdown themselves, but requires the scheduler
terminationFuture onComplete (_ stopScheduler())
}
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: Closeable
// Let dispatchers shutdown first.
// Dispatchers schedule shutdown and may also reschedule, therefore wait 4 times the shutdown delay.
x.scheduleOnce(() { x.close(); dispatcher.shutdown() }, settings.DispatcherDefaultShutdown * 4)
case _
}
private val extensions = new ConcurrentIdentityHashMap[ExtensionId[_], AnyRef]

View file

@ -57,9 +57,6 @@ trait BootableActorLoaderService extends Bootable {
abstract override def onUnload() = {
super.onUnload()
// FIXME shutdown all actors
// system.registry.local.shutdownAll
}
}

View file

@ -18,7 +18,6 @@ import com.typesafe.config.Config
trait ActorDeployer {
private[akka] def init(deployments: Seq[Deploy]): Unit
private[akka] def shutdown(): Unit //TODO Why should we have "shutdown", should be crash only?
private[akka] def deploy(deployment: Deploy): Unit
private[akka] def lookupDeploymentFor(path: String): Option[Deploy]
def lookupDeployment(path: String): Option[Deploy] = path match {
@ -49,8 +48,6 @@ class Deployer(val settings: ActorSystem.Settings, val eventStream: EventStream,
private[akka] def init(deployments: Seq[Deploy]) = instance.init(deployments)
def shutdown(): Unit = instance.shutdown() //TODO FIXME Why should we have "shutdown", should be crash only?
def deploy(deployment: Deploy): Unit = instance.deploy(deployment)
def isLocal(deployment: Deploy): Boolean = deployment match {
@ -88,7 +85,7 @@ class Deployer(val settings: ActorSystem.Settings, val eventStream: EventStream,
}
import scala.collection.JavaConverters._
settings.config.getConfig("akka.actor.deployment").toObject.keySet.asScala
settings.config.getConfig("akka.actor.deployment").root.keySet.asScala
.filterNot("default" ==)
.map(path pathSubstring(path))
.toSet.toList // toSet to force uniqueness

View file

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

View file

@ -69,6 +69,7 @@ final case class TaskInvocation(eventStream: EventStream, function: () ⇒ Unit,
try {
function()
} catch {
// FIXME catching all and continue isn't good for OOME, ticket #1418
case e eventStream.publish(Error(e, "TaskInvocation", e.getMessage))
} finally {
cleanup()
@ -135,7 +136,7 @@ abstract class MessageDispatcher(val prerequisites: DispatcherPrerequisites) ext
shutdownScheduleUpdater.get(this) match {
case UNSCHEDULED
if (shutdownScheduleUpdater.compareAndSet(this, UNSCHEDULED, SCHEDULED)) {
scheduler.scheduleOnce(shutdownAction, Duration(shutdownTimeout.toMillis, TimeUnit.MILLISECONDS))
scheduler.scheduleOnce(shutdownAction, shutdownTimeout)
()
} else ifSensibleToDoSoThenScheduleShutdown()
case SCHEDULED
@ -210,7 +211,7 @@ abstract class MessageDispatcher(val prerequisites: DispatcherPrerequisites) ext
}
case RESCHEDULED
if (shutdownScheduleUpdater.compareAndSet(MessageDispatcher.this, RESCHEDULED, SCHEDULED))
scheduler.scheduleOnce(this, Duration(shutdownTimeout.toMillis, TimeUnit.MILLISECONDS))
scheduler.scheduleOnce(this, shutdownTimeout)
else run()
}
}

View file

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

View file

@ -27,10 +27,7 @@ class FutureTimeoutException(message: String, cause: Throwable = null) extends A
def this(message: String) = this(message, null)
}
class FutureFactory(dispatcher: MessageDispatcher, timeout: Timeout) {
// TODO: remove me ASAP !!!
implicit val _dispatcher = dispatcher
class FutureFactory()(implicit dispatcher: MessageDispatcher, timeout: Timeout) {
/**
* Java API, equivalent to Future.apply
@ -163,6 +160,7 @@ object Future {
try {
Right(body)
} catch {
// FIXME catching all and continue isn't good for OOME, ticket #1418
case e Left(e)
}
}
@ -411,7 +409,9 @@ object Future {
try {
next.apply()
} catch {
case e e.printStackTrace() //TODO FIXME strategy for handling exceptions in callbacks
case e
// FIXME catching all and continue isn't good for OOME, ticket #1418
dispatcher.prerequisites.eventStream.publish(Error(e, "Future.dispatchTask", "Failed to dispatch task, due to: " + e.getMessage))
}
}
} finally { _taskStack set None }
@ -984,7 +984,7 @@ class DefaultPromise[T](val timeout: Timeout)(implicit val dispatcher: MessageDi
def run() {
if (!isCompleted) {
if (!isExpired) dispatcher.prerequisites.scheduler.scheduleOnce(this, Duration(timeLeftNoinline(), TimeUnit.NANOSECONDS))
else promise complete (try { Right(fallback) } catch { case e Left(e) })
else promise complete (try { Right(fallback) } catch { case e Left(e) }) // FIXME catching all and continue isn't good for OOME, ticket #1418
}
}
}
@ -994,6 +994,7 @@ class DefaultPromise[T](val timeout: Timeout)(implicit val dispatcher: MessageDi
} else this
private def notifyCompleted(func: Future[T] Unit) {
// FIXME catching all and continue isn't good for OOME, ticket #1418
try { func(this) } catch { case e dispatcher.prerequisites.eventStream.publish(Error(e, "Future", "Future onComplete-callback raised an exception")) } //TODO catch, everything? Really?
}

View file

@ -28,9 +28,6 @@ object Mailbox {
// secondary status: Scheduled bit may be added to Open/Suspended
final val Scheduled = 4
// mailbox debugging helper using println (see below)
// FIXME TODO take this out before release
final val debug = false
}
/**
@ -40,13 +37,13 @@ abstract class Mailbox(val actor: ActorCell) extends MessageQueue with SystemMes
import Mailbox._
@volatile
protected var _status: Status = _ //0 by default
protected var _statusDoNotCallMeDirectly: Status = _ //0 by default
@volatile
protected var _systemQueue: SystemMessage = _ //null by default
protected var _systemQueueDoNotCallMeDirectly: SystemMessage = _ //null by default
@inline
final def status: Mailbox.Status = _status
final def status: Mailbox.Status = Unsafe.instance.getIntVolatile(this, AbstractMailbox.mailboxStatusOffset)
@inline
final def shouldProcessMessage: Boolean = (status & 3) == Open
@ -65,7 +62,8 @@ abstract class Mailbox(val actor: ActorCell) extends MessageQueue with SystemMes
Unsafe.instance.compareAndSwapInt(this, AbstractMailbox.mailboxStatusOffset, oldStatus, newStatus)
@inline
protected final def setStatus(newStatus: Status): Unit = _status = newStatus
protected final def setStatus(newStatus: Status): Unit =
Unsafe.instance.putIntVolatile(this, AbstractMailbox.mailboxStatusOffset, newStatus)
/**
* set new primary status Open. Caller does not need to worry about whether
@ -130,7 +128,8 @@ abstract class Mailbox(val actor: ActorCell) extends MessageQueue with SystemMes
/*
* AtomicReferenceFieldUpdater for system queue
*/
protected final def systemQueueGet: SystemMessage = _systemQueue
protected final def systemQueueGet: SystemMessage =
Unsafe.instance.getObjectVolatile(this, AbstractMailbox.systemMessageOffset).asInstanceOf[SystemMessage]
protected final def systemQueuePut(_old: SystemMessage, _new: SystemMessage): Boolean =
Unsafe.instance.compareAndSwapObject(this, AbstractMailbox.systemMessageOffset, _old, _new)
@ -165,7 +164,6 @@ abstract class Mailbox(val actor: ActorCell) extends MessageQueue with SystemMes
var processedMessages = 0
val deadlineNs = if (dispatcher.isThroughputDeadlineTimeDefined) System.nanoTime + dispatcher.throughputDeadlineTime.toNanos else 0
do {
if (debug) println(actor.self + " processing message " + nextMessage)
actor invoke nextMessage
processAllSystemMessages() //After we're done, process all system messages
@ -188,7 +186,6 @@ abstract class Mailbox(val actor: ActorCell) extends MessageQueue with SystemMes
var nextMessage = systemDrain()
try {
while (nextMessage ne null) {
if (debug) println(actor.self + " processing system message " + nextMessage + " with children " + actor.childrenRefs)
actor systemInvoke nextMessage
nextMessage = nextMessage.next
// dont ever execute normal message when system message present!
@ -243,7 +240,6 @@ trait DefaultSystemMessageQueue { self: Mailbox ⇒
@tailrec
final def systemEnqueue(receiver: ActorRef, message: SystemMessage): Unit = {
assert(message.next eq null)
if (Mailbox.debug) println(actor.self + " having enqueued " + message)
val head = systemQueueGet
/*
* this write is safely published by the compareAndSet contained within

View file

@ -11,6 +11,9 @@ import akka.event.Logging.{ Warning, Error }
import akka.actor.ActorSystem
import java.util.concurrent._
import akka.event.EventStream
import concurrent.forkjoin.ForkJoinPool._
import concurrent.forkjoin.{ ForkJoinTask, ForkJoinWorkerThread, ForkJoinPool }
import concurrent.forkjoin.ForkJoinTask._
object ThreadPoolConfig {
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
*/

View file

@ -80,7 +80,7 @@ trait LoggingBus extends ActorEventBus {
loggers = Seq(StandardOutLogger)
_logLevel = level
}
publish(Info(simpleName(this), "StandardOutLogger started"))
publish(Debug(simpleName(this), "StandardOutLogger started"))
}
private[akka] def startDefaultLoggers(system: ActorSystemImpl) {
@ -113,7 +113,7 @@ trait LoggingBus extends ActorEventBus {
loggers = myloggers
_logLevel = level
}
publish(Info(simpleName(this), "Default Loggers started"))
publish(Debug(simpleName(this), "Default Loggers started"))
if (!(defaultLoggers contains StandardOutLoggerName)) {
unsubscribe(StandardOutLogger)
}
@ -153,7 +153,7 @@ trait LoggingBus extends ActorEventBus {
if (response != LoggerInitialized)
throw new LoggerInitializationException("Logger " + name + " did not respond with LoggerInitialized, sent instead " + response)
AllLogLevels filter (level >= _) foreach (l subscribe(actor, classFor(l)))
publish(Info(simpleName(this), "logger " + name + " started"))
publish(Debug(simpleName(this), "logger " + name + " started"))
actor
}

View file

@ -0,0 +1,43 @@
/**
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.event
import akka.actor.Actor.Receive
import akka.actor.ActorSystem
import akka.event.Logging.Debug
object LoggingReceive {
/**
* Wrap a Receive partial function in a logging enclosure, which sends a
* debug message to the event bus each time before a message is matched.
* This includes messages which are not handled.
*
* <pre><code>
* def receive = LoggingReceive(this) {
* case x => ...
* }
* </code></pre>
*
* This method does NOT modify the given Receive unless
* akka.actor.debug.receive is set within akka.conf.
*/
def apply(source: AnyRef)(r: Receive)(implicit system: ActorSystem): Receive = r match {
case _: LoggingReceive r
case _ if !system.settings.AddLoggingReceive r
case _ new LoggingReceive(source, r)
}
}
/**
* This decorator adds invocation logging to a Receive function.
*/
class LoggingReceive(source: AnyRef, r: Receive)(implicit system: ActorSystem) extends Receive {
def isDefinedAt(o: Any) = {
val handled = r.isDefinedAt(o)
system.eventStream.publish(Debug(LogSource.fromAnyRef(source), "received " + (if (handled) "handled" else "unhandled") + " message " + o))
handled
}
def apply(o: Any): Unit = r(o)
}

View file

@ -11,6 +11,7 @@ import scala.annotation.tailrec
import java.util.concurrent.atomic.{ AtomicReference, AtomicInteger }
import java.net.InetSocketAddress
import akka.remote.RemoteAddress
import collection.JavaConverters
/**
* An Iterable that also contains a version.
@ -85,6 +86,10 @@ trait 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] {
def iterable = connections
}

View file

@ -9,11 +9,11 @@ import akka.actor._
import akka.config.ConfigurationException
import akka.dispatch.{ Future, MessageDispatcher }
import akka.util.{ ReflectiveAccess, Duration }
import java.net.InetSocketAddress
import java.lang.reflect.InvocationTargetException
import java.util.concurrent.atomic.{ AtomicReference, AtomicInteger }
import scala.annotation.tailrec
import akka.japi.Creator
sealed trait RouterType
@ -76,6 +76,12 @@ case class RoutedProps private[akka] (
connectionManager: ConnectionManager,
timeout: Timeout = RoutedProps.defaultTimeout,
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 {
@ -245,7 +251,7 @@ trait BasicRouter extends Router {
next match {
case Some(connection)
try {
connection.?(message, timeout).asInstanceOf[Future[T]] //FIXME this does not preserve the original sender, shouldn't it?
connection.?(message, timeout).asInstanceOf[Future[T]] //FIXME this does not preserve the original sender, shouldn't it??
} catch {
case e: Exception
connectionManager.remove(connection)

View file

@ -7,8 +7,7 @@ package akka.serialization
import akka.AkkaException
import akka.util.ReflectiveAccess
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.actor.{ Extension, ActorSystem, ActorSystemImpl }
@ -19,11 +18,7 @@ object Serialization {
// TODO ensure that these are always set (i.e. withValue()) when doing deserialization
val currentSystem = new DynamicVariable[ActorSystemImpl](null)
class Settings(cfg: 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()
class Settings(val config: Config) {
import scala.collection.JavaConverters._
import config._
@ -37,7 +32,7 @@ object Serialization {
hasPath(configPath) match {
case false Map()
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 invalid throw new ConfigurationException("Invalid serialization-bindings [%s]".format(invalid))
}
@ -47,7 +42,7 @@ object Serialization {
}
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 {
import Serialization._
val settings = new Settings(system.applicationConfig)
val settings = new Settings(system.settings.config)
//TODO document me
def serialize(o: AnyRef): Either[Exception, Array[Byte]] =

View file

@ -108,6 +108,7 @@ class BoundedBlockingQueue[E <: AnyRef](
throw ie
}
false
// FIXME catching all and continue isn't good for OOME, ticket #1418
case e
notFull.signal()
result = e
@ -234,7 +235,7 @@ class BoundedBlockingQueue[E <: AnyRef](
if (backing.removeAll(c)) {
val sz = backing.size()
if (sz < maxCapacity) notFull.signal()
if (sz > 0) notEmpty.signal() //FIXME needed?
if (sz > 0) notEmpty.signal() //FIXME needed??
true
} else false
} finally {
@ -247,7 +248,7 @@ class BoundedBlockingQueue[E <: AnyRef](
try {
if (backing.retainAll(c)) {
val sz = backing.size()
if (sz < maxCapacity) notFull.signal() //FIXME needed?
if (sz < maxCapacity) notFull.signal() //FIXME needed??
if (sz > 0) notEmpty.signal()
true
} else false

View file

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

View file

@ -6,7 +6,6 @@ import org.scalatest.matchers.MustMatchers
//#imports
import akka.actor.ActorSystem
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
//#imports
@ -21,7 +20,7 @@ class ConfigDocSpec extends WordSpec {
nr-of-instances = 3
}
}
""", ConfigParseOptions.defaults)
""")
val system = ActorSystem("MySystem", ConfigFactory.systemProperties.withFallback(customConf))
//#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
configuration.
FIXME: These default locations has changed
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.
@ -44,47 +46,42 @@ Each Akka module has a reference configuration file with the default values.
*akka-actor:*
.. literalinclude:: ../../akka-actor/src/main/resources/akka-actor-reference.conf
.. literalinclude:: ../../akka-actor/src/main/resources/reference.conf
:language: none
*akka-remote:*
.. literalinclude:: ../../akka-remote/src/main/resources/akka-remote-reference.conf
.. literalinclude:: ../../akka-remote/src/main/resources/reference.conf
:language: none
*akka-serialization:*
.. literalinclude:: ../../akka-actor/src/main/resources/akka-serialization-reference.conf
:language: none
*akka-testkit:*
.. literalinclude:: ../../akka-testkit/src/main/resources/akka-testkit-reference.conf
.. literalinclude:: ../../akka-testkit/src/main/resources/reference.conf
:language: none
*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
*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
*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
*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
*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
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
------------------
@ -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>`_
specification. Note that it supports three formats; conf, json, and properties.
.. _-Dakka.mode:
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
``-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
@ -152,6 +152,8 @@ The mode option is not used when specifying the configuration file with ``-Dakka
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
environment independent settings and then override some settings for specific modes.
@ -165,14 +167,14 @@ akka.dev.conf:
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
null, then the source from which Akka reads its configuration is printed to the
console during application startup.
If the system or config property ``akka.logConfigOnStart`` is set to ``on``, then the
complete configuration at INFO level when the actor system is started. This is useful
when you are uncertain of what configuration is used.
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.config <-Dakka.config>`: explicit configuration file location
* :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 {
// A simple Actor that just prints the messages it processes
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 {
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
ActorRef ref = Actors.actorOf(MyActor.class);
// We create a new Priority dispatcher and seed it with the priority generator
ref.setDispatcher(new Dispatcher("foo", 5, new UnboundedPriorityMailbox(gen)));
// 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.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
It also prevents the client from invoking any life-cycle and side-effecting methods, such as:
* start
* stop
* startsWatching
* stopsWatching
* etc.
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
Props(new Actor {
self ! 'lowpriority
self ! 'lowpriority
self ! 'highpriority
self ! 'pigdog
self ! 'pigdog2
self ! 'pigdog3
self ! 'highpriority
def receive = {
case x => println(x)
}
}).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:
'highpriority

View file

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

View file

@ -717,11 +717,12 @@ options:
``akka.actor.debug.receive`` — which enables the :meth:`loggable`
statement to be applied to an actors :meth:`receive` function::
def receive = Actor.loggable(this) { // `Actor` unnecessary with import Actor._
import akka.event.LoggingReceive
def receive = LoggingReceive(this) {
case msg => ...
}
The first argument to :meth:`loggable` defines the source to be used in the
The first argument to :meth:`LoggingReceive` defines the source to be used in the
logging events, which should be the current actor.
If the abovementioned setting is not given in ``akka.conf``, this method will

View file

@ -4,23 +4,16 @@
package akka.actor.mailbox
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 java.util.concurrent.TimeUnit.MILLISECONDS
import akka.actor._
object BeanstalkBasedMailboxExtension extends ExtensionId[BeanstalkMailboxSettings] with ExtensionIdProvider {
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 {
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()
class BeanstalkMailboxSettings(val config: Config) extends Extension {
import config._

View file

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

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