我正在开发一个用 Scala 编写的小型 GUI 应用程序。用户将在 GUI 中设置一些设置,我希望它们在程序执行之间保持不变。基本上我想要一个 scala.collections.mutable.Map 在修改时自动保存到文件中。
这似乎一定是一个普遍的问题,但我一直无法找到一个轻量级的解决方案。这个问题通常是如何解决的?
我正在开发一个用 Scala 编写的小型 GUI 应用程序。用户将在 GUI 中设置一些设置,我希望它们在程序执行之间保持不变。基本上我想要一个 scala.collections.mutable.Map 在修改时自动保存到文件中。
这似乎一定是一个普遍的问题,但我一直无法找到一个轻量级的解决方案。这个问题通常是如何解决的?
我做了很多这样的事情,并且我使用 .properties 文件(它在 Java 领域是惯用的)。不过,我在设计上保持我的配置非常简单。如果您有嵌套的配置结构,您可能需要不同的格式,例如 YAML(如果人类是主要作者)或 JSON 或 XML(如果机器是作者)。
下面是一些示例代码,用于加载道具、操作为 Scala Map,然后再次保存为 .properties:
import java.io._
import java.util._
import scala.collection.JavaConverters._
val f = new File("test.properties")
// test.properties:
// foo=bar
// baz=123
val props = new Properties
// Note: in real code make sure all these streams are
// closed carefully in try/finally
val fis = new InputStreamReader(new FileInputStream(f), "UTF-8")
props.load(fis)
fis.close()
println(props) // {baz=123, foo=bar}
val map = props.asScala // Get to Scala Map via JavaConverters
map("foo") = "42"
map("quux") = "newvalue"
println(map) // Map(baz -> 123, quux -> newvalue, foo -> 42)
println(props) // {baz=123, quux=newvalue, foo=42}
val fos = new OutputStreamWriter(new FileOutputStream(f), "UTF-8")
props.store(fos, "")
fos.close()
由于这一切都归结为将映射/对象序列化为文件,因此您的选择是:
这是一个使用 XML 和案例类来读取配置的示例。真正的班级可能比地图更好。(您也可以做 sbt 和至少一个项目所做的事情,将配置作为 Scala 源并编译进去;保存它的自动化程度较低。或者作为 repl 脚本。我没有用谷歌搜索,但一定有人这样做了。 )
此版本使用案例类:
case class PluginDescription(name: String, classname: String) {
def toXML: Node = {
<plugin>
<name>{name}</name>
<classname>{classname}</classname>
</plugin>
}
}
object PluginDescription {
def fromXML(xml: Node): PluginDescription = {
// extract one field
def getField(field: String): Option[String] = {
val text = (xml \\ field).text.trim
if (text == "") None else Some(text)
}
def extracted = {
val name = "name"
val claas = "classname"
val vs = Map(name -> getField(name), claas -> getField(claas))
if (vs.values exists (_.isEmpty)) fail()
else PluginDescription(name = vs(name).get, classname = vs(claas).get)
}
def fail() = throw new RuntimeException("Bad plugin descriptor.")
// check the top-level tag
xml match {
case <plugin>{_*}</plugin> => extracted
case _ => fail()
}
}
}
此代码反射性地调用案例类的应用。用例是配置中缺少的字段可以由默认参数提供。这里没有类型转换。例如,case class Config(foo: String = "bar")
。
// isn't it easier to write a quick loop to reflect the field names?
import scala.reflect.runtime.{currentMirror => cm, universe => ru}
import ru._
def fromXML(xml: Node): Option[PluginDescription] = {
def extract[A]()(implicit tt: TypeTag[A]): Option[A] = {
// extract one field
def getField(field: String): Option[String] = {
val text = (xml \\ field).text.trim
if (text == "") None else Some(text)
}
val apply = ru.newTermName("apply")
val module = ru.typeOf[A].typeSymbol.companionSymbol.asModule
val ts = module.moduleClass.typeSignature
val m = (ts member apply).asMethod
val im = cm reflect (cm reflectModule module).instance
val mm = im reflectMethod m
def getDefault(i: Int): Option[Any] = {
val n = ru.newTermName("apply$default$" + (i+1))
val m = ts member n
if (m == NoSymbol) None
else Some((im reflectMethod m.asMethod)())
}
def extractArgs(pss: List[List[Symbol]]): List[Option[Any]] =
pss.flatten.zipWithIndex map (p => getField(p._1.name.encoded) orElse getDefault(p._2))
val args = extractArgs(m.paramss)
if (args exists (!_.isDefined)) None
else Some(mm(args.flatten: _*).asInstanceOf[A])
}
// check the top-level tag
xml match {
case <plugin>{_*}</plugin> => extract[PluginDescription]()
case _ => None
}
}
XML 有loadFile
,而且save
,太糟糕了,似乎没有针对Properties
.
$ scala
Welcome to Scala version 2.10.0-RC5 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_06).
Type in expressions to have them evaluated.
Type :help for more information.
scala> import reflect.io._
import reflect.io._
scala> import java.util._
import java.util._
scala> import java.io.{StringReader, File=>JFile}
import java.io.{StringReader, File=>JFile}
scala> import scala.collection.JavaConverters._
import scala.collection.JavaConverters._
scala> val p = new Properties
p: java.util.Properties = {}
scala> p load new StringReader(
| (new File(new JFile("t.properties"))).slurp)
scala> p.asScala
res2: scala.collection.mutable.Map[String,String] = Map(foo -> bar)
I suggest to convert Map to Properties and vice versa. "*.properties" files are standard for storing configuration in Java world, why not use it for Scala?
常见的方式是*。属性,*.xml,因为 scala 本身支持 xml,所以使用 xml 配置比在 java 中更容易。