Skip to content

Instantly share code, notes, and snippets.

@kirked
Created October 11, 2019 14:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kirked/827d7640da4eaae6c267a7083d191f2c to your computer and use it in GitHub Desktop.
Save kirked/827d7640da4eaae6c267a7083d191f2c to your computer and use it in GitHub Desktop.
Scala syntax augmentation for Typesafe config
import com.typesafe.config.{Config, ConfigException, ConfigFactory}
import java.time.{Duration => JDuration}
import scala.collection.JavaConverters._
import scala.reflect.ClassTag
import scala.util.Try
object config {
class ConfigOps(config: Config) {
def booleanOption(path: String): Option[Boolean] =
if (config.hasPath(path)) Some(config.getBoolean(path)) else None
def booleanOrElse(path: String, default: Boolean): Boolean =
booleanOption(path) getOrElse default
def intOption(path: String): Option[Int] =
if (config.hasPath(path)) Some(config.getInt(path)) else None
def intOrElse(path: String, default: Int): Int =
intOption(path) getOrElse default
def intList(path: String): List[Int] =
if (config.hasPath(path)) config.getIntList(path).asScala.toList.map(_.intValue) else Nil
def stringOption(path: String): Option[String] =
if (config.hasPath(path)) Some(config.getString(path)) else None
def stringOrElse(path: String, default: String): String =
stringOption(path) getOrElse default
def stringList(path: String): List[String] =
if (config.hasPath(path)) config.getStringList(path).asScala.toList else Nil
def configOption(path: String): Option[Config] =
if (config.hasPath(path)) Some(config.getConfig(path)) else None
def configOrElse(path: String, default: Config): Config =
configOption(path) getOrElse default
def configOrEmpty(path: String): Config =
configOrElse(path, ConfigFactory.empty)
def configList(path: String): List[Config] =
if (config.hasPath(path)) config.getConfigList(path).asScala.toList else Nil
def configAsStringMap(path: String): Map[String, String] = {
if (config.hasPath(path)) config.getConfig(path).entrySet.asScala.foldLeft(Map.empty[String, String]) {
case (map, entry) => map + (entry.getKey -> entry.getValue.unwrapped.toString)
}
else Map.empty
}
def durationOption(path: String): Option[JDuration] =
if (config.hasPath(path)) Some(config.getDuration(path)) else None
def getInstance[A: ClassTag](key: String): A = {
val expectedClass = implicitly[ClassTag[A]].runtimeClass
/*
* Load the value by calling a factory method inside a Scala object.
*
* Keep in mind that Scala `val`s are JVM methods.
*/
def loadFromFactoryMethod(objectClass: Class[_], obj: AnyRef): Option[A] = {
val fullKey = s"$key.factory-method"
for {
methodName <- stringOption(fullKey)
method <- Try(objectClass.getMethod(methodName)).toOption
result <- Try(method.invoke(obj).asInstanceOf[A]).toOption
} yield result
}
def acceptableClass(klass: Class[_]): Boolean =
(expectedClass isAssignableFrom klass)
def is(obj: AnyRef): Option[A] = {
if (acceptableClass(obj.getClass)) Some(obj.asInstanceOf[A])
else None
}
/*
* A Scala object is the singleton instance of the value,
* or provides a factory method for the value.
*/
def loadFromObject(): Option[A] = {
val fullKey = s"$key.object"
val resultOpt = for {
objectName <- stringOption(fullKey)
objectClass <- loadClass(objectName)
instance <- Try(objectClass.getField("MODULE$").get(null)).toOption
} yield {
is(instance) orElse loadFromFactoryMethod(objectClass, instance)
}
resultOpt.flatten
}
def loadFromNewClassInstance(): Option[A] = {
val fullKey = s"$key.class"
for {
fqcn <- stringOption(fullKey)
klass <- loadClass(fqcn) if acceptableClass(klass)
instance <- Try(klass.getConstructor().newInstance().asInstanceOf[Object]).toOption
casted <- is(instance)
} yield casted
}
(loadFromObject orElse loadFromNewClassInstance) match {
case Some(a: A) => a
case None =>
val where = config.origin.description
val msg = s"one of '$key.class'; '$key.object' (optionally with '$key.factory-method')"
throw new ConfigException.Missing(s"$where at $key, expected an instance specification, $msg")
}
}
private[this] def loadClass(fqcn: String): Option[Class[_]] = {
Try(Thread.currentThread.getContextClassLoader.loadClass(fqcn)).toOption
}
}
implicit def configSyntax(config: Config) = new ConfigOps(config)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment