2012-12-18 18 views
8

Pracuję nad małą aplikacją GUI napisaną w Scali. Istnieje kilka ustawień, które użytkownik ustawi w GUI i chcę, aby utrzymywały się między kolejnymi uruchomieniami programu. Zasadniczo chcę scala.collections.mutable.Map, która automatycznie utrzymuje się w pliku po modyfikacji.Jak zapewnić podstawową konfigurację aplikacji Scala?

Wydaje się, że musi to być typowy problem, ale nie byłem w stanie znaleźć lekkiego rozwiązania. W jaki sposób rozwiązuje się ten problem?

+0

Skończyłem serializowanie do JSON za pomocą json4s https://github.com/json4s/json4s Dzięki za wszystkie sugestie. – Dave

Odpowiedz

9

Zrobiłem dużo tego i używam plików .properties (jest to idiomatyczne w Java-land). Jednak utrzymuję konfigurację dość prosto z projektu. Jeśli masz zagnieżdżone konstrukty konfiguracyjne, możesz chcieć użyć innego formatu, np. YAML (jeśli głównym autorem są ludzie) lub JSON lub XML (jeśli autorzy są autorami).

Oto niektóre przykładowy kod dla załadunku rekwizytów, manipulując jak Scala mapą, a następnie zapisywanie jako .Properties ponownie:

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() 
+0

Czy asScala działa dla typów zagnieżdżonych?Powiedzmy, że ładuję coś w rodzaju obiektu JSON - może będę chciał odzyskać mapę, na której niektóre jej wartości są innymi mapami lub listami, zawierającą inne mapy, listy, liczby, łańcuchy itp. – user48956

0

Proponuję przekonwertować mapę na właściwości i odwrotnie. Pliki "* .properties" są standardem do przechowywania konfiguracji w świecie Java, dlaczego nie używać go do Scala?

0

Typowym sposobem jest *. właściwości, * .xml, ponieważ scala obsługuje xml natywnie, więc byłoby łatwiej przy użyciu xml config, a następnie w java.

1

Oto przykład użycia XML i klasę etui do czytania config. Prawdziwa klasa może być ładniejsza niż mapa. (Możesz także zrobić to, co sbt i co najmniej jeden projekt, wziąć konfigurację jako źródło Scala i skompilować ją, zapisanie jej jest mniej automatyczne lub jako skrypt replikacji. Nie mam google'a, ale ktoś musiał to zrobić.)

Here's the simpler code.

Ta wersja wykorzystuje klasę obudowy:

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() 
    } 
    } 
} 

Ten kod refleksyjnie wywołuje zastosowanie klasy sprawy. Przypadek użycia jest taki, że pola brakujące w config mogą być dostarczane jako domyślne argumenty. Brak konwersji typu tutaj. Np., 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 ma loadFile i save, szkoda wydaje się, że nikt za Properties-liner.

$ 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) 
1

Jak to wszystko sprowadza się do szeregowania na mapę/przedmiot do pliku, do wyboru są:

  • klasyczny serializacji do kodu bajtowego
  • serializacji XML
  • serializacji do JSON (łatwe użycie Jackson lub Lift-JSON)
  • użycie pliku właściwości (brzydki, brak obsługi utf-8)
  • serializacja do propra ietary format (brzydki, dlaczego reinvent the wheel)
Powiązane problemy