scaladynamicscala-3

Looking up and setting elements of java.util.Properties using dot notation in Scala


I am working through Scala For the Impatient by Horstman. I'm not doing this for a class. In the book, he has code that dynamically looks up and sets elements from an instance of java.util.Properties.

package dynamicProperties

import scala.language.dynamics

class DynamicProps(val props: java.util.Properties) extends Dynamic:
  def updateDynamic(name: String)(value: String): AnyRef =
    props.setProperty(name.replaceAll("_", ".'"), value)
  def selectDynamic(name: String): String =
    props.getProperty(name.replaceAll("_", "."))
  def applyDynamicNamed(name: String)(args: (String, String)*): Any =
    if name != "add" then throw IllegalArgumentException()
    for (k, v) <- args do
      props.setProperty(k.replaceAll("_", "."), v)

I wrote up some test code.

import dynamicProperties.DynamicProps
import org.scalatest.funsuite.AnyFunSuite

class DynamicPropsTest extends AnyFunSuite:
  test("Set username") {
    val sysProps = DynamicProps(System.getProperties)
    sysProps.username = "Fred"
    assert(sysProps.username == "Fred")
  }
  test("Assign java.home") {
    val sysProps = DynamicProps(System.getProperties)
    val home = sysProps.java_home
    val javaHome = System.getProperty("java.home")
    assert(home == javaHome)
  }
  test("Add key/value pairs") {
    val sysProps = DynamicProps(System.getProperties)
    sysProps.add(username="Fred", password="Secret")
    assert(sysProps.username == "Fred")
    assert(sysProps.password == "Secret")
  }

In the book, the reader is supposed to figure out a way to select and set properties without an underscore. For example, you should be able to do this:

val sysProps = DynamicProps(System.getProperties)
val home = sysProps.java.home

I am not sure how to do this. I know if I remove the code that replaces underscores with periods in the class I get this error if run the above snippet.

value home is not a member of String
    val home = sysProps.java.home

Any hints?


Solution

  • Since it's been a week since @DanGetz's hints, I guess now it's safe to share a partial solution. You can apply Dynamic logic recursively:

    class DynamicProps(
      props: java.util.Properties,
      propName: String = "", 
      prop: Option[String] = None,
    ) extends Dynamic:
      def selectDynamic(name: String): DynamicProps =
        val newName = if propName.isEmpty then name else propName + "." + name
        val newProp = Option(props.getProperty(newName))
        DynamicProps(props, newName, newProp)
    
      override def toString: String = prop.getOrElse("n/a")
    

    Then

    val props = DynamicProps({
      val p = java.util.Properties()
      p.setProperty("xxx", "aaa")
      p.setProperty("xxx.yyy", "bbb")
      p
    })
    
    props.xxx // aaa
    props.xxx.yyy // bbb
      
    val sysProps = DynamicProps(System.getProperties)
    sysProps.xxx // n/a
    sysProps.java.home // /media/data/jdk1.8.0_351/jre
    sysProps.java.runtime.name // Java(TM) SE Runtime Environment