Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

I was thinking about a solution to store preferences and access to them in a most natural way. I came up with the solution below. My questions are: what do you think about the solution? Can it be improved (except maybe using macros)? Is it too complicated (to use)?

And most important: does anyone know of a project where something like this has already been solved? Maybe even using macros? Could also be done using ".properties"-files or some other file-based storage. Thanks.

trait PrefsStorage {
  def get(key: String, default: String): String
  def put(key: String, newValue: String)
}

abstract class UserPrefs[P <% PrefsStorage, T] {
  protected val prefs: P
  protected val key: String
  protected val default: T
  private[this] var bufferedValue: T = fromString(prefs.get(key, stringFrom(default)))
  // conversion from and to String
  def fromString(str: String): T
  def stringFrom(newValue: T): String = newValue.toString
  // setter and getter
  final def value_=(newValue: T) = { prefs.put(key, stringFrom(newValue)); bufferedValue = newValue }
  final def value: T = bufferedValue
      // setter and getter for easier access from the outside
  final def getter() = value
  final def setter = {value_=_}
}

abstract class DefaultUserPrefs[P <% PrefsStorage, T](protected val prefs: P, protected val key: String, protected val default: T) extends UserPrefs[P, T]

class FunUserPrefs[P <% PrefsStorage, T](protected val prefs: P, protected val key: String, protected val default: T,
                                         convertFromString: String => T) extends UserPrefs[P, T] {
  def fromString(str: String) = convertFromString(str)
}

class IntUserPrefs[P <% PrefsStorage](protected val prefs: P, protected val key: String, protected val default: Int) extends UserPrefs[P, Int] {
  def fromString(str: String) = str.toInt
}
class BooleanUserPrefs[P <% PrefsStorage](protected val prefs: P, protected val key: String, protected val default: Boolean) extends UserPrefs[P, Boolean] {
  def fromString(str: String) = str.toBoolean
}
class StringUserPrefs[P <% PrefsStorage](protected val prefs: P, protected val key: String, protected val default: String) extends UserPrefs[P, String] {
  def fromString(str: String) = str
}
class DoubleUserPrefs[P <% PrefsStorage](protected val prefs: P, protected val key: String, protected val default: Double) extends UserPrefs[P, Double] {
  def fromString(str: String) = str.toDouble
}

// Helper object to create preferences
object UserPrefs {
  class UserPrefsCreate[P <% PrefsStorage](val prefs: P) {
    def apply(key: String, default: Int) = new IntUserPrefs[P](prefs, key, default)
    def apply(key: String, default: Boolean) = new BooleanUserPrefs[P](prefs, key, default)
    def apply(key: String, default: String) = new StringUserPrefs[P](prefs, key, default)
    def apply(key: String, default: Double) = new DoubleUserPrefs[P](prefs, key, default)
    def apply[T](key: String, default: T, fun: String => T) = new FunUserPrefs[P, T](prefs, key, default, fun)
  }
  def apply(prefs: PrefsStorage) = new UserPrefsCreate(prefs)
}

    // some implicit helpers
object UseAsPreferencesImplicits {
  import java.util.prefs.Preferences
  import scala.language.implicitConversions
  implicit def preferences2PrefsStorage(prefs: Preferences) = new PrefsStorage {
    def get(key: String, default: String) = prefs.get(key, default)
    def put(key: String, newValue: String) { prefs.put(key, newValue) }
  }
}

object UserPrefsImplicits {
  import scala.language.implicitConversions
  implicit def UserPrefsToType[P <% PrefsStorage, T](up: UserPrefs[P, T]) = up.value
  implicit def userPrefs2Getter[T](userPrefs: UserPrefs[PrefsStorage, T]) = userPrefs.getter
  implicit def userPrefs2Setter[T](userPrefs: UserPrefs[PrefsStorage, T]) = userPrefs.setter
}

The basic types String, Int, Double and Boolean are wrapped easily. But also tuples, arrays, etc. can be extended. The usage is displayed in the test code below. I haven't written unit tests (yet), but the sample progam should work nicely, storing some values to the user preferences.

object TestUserPrefs extends App {
  class Sample {
    import UseAsPreferencesImplicits._
    import java.util.prefs.Preferences.userNodeForPackage
    // sample usage of UserPrefsCreate
    protected val createPrefs = UserPrefs(userNodeForPackage(Sample.this.getClass))
    // numTuple and numberOfCalls are saved to the preferences
    val numTuple = createPrefs("numTuple", (1, 2), str => {
      val idx = str.indexOf(',')
      str.substring(1, idx).toInt -> str.substring(idx + 1, str.length - 1).toInt
    })
    val intArray = new DefaultUserPrefs(createPrefs.prefs, "intArray", Array(8, 9)) {
      override def stringFrom(newValue: Array[Int]) = newValue.mkString(",")
      def fromString(str: String) = str.split(',').map(_.toInt)
    }
    val numberOfCalls = createPrefs("numberOfCalls", 0)
  }

  val sample = new Sample
  println(sample.numTuple.value) // (1,2) when executed the first time, (5,6) afterwards
  sample.numTuple.value = (3, 4)
  println(sample.numTuple.value) // (3,4)
  sample.numTuple.value = (5, 6)
  println(sample.numTuple.value) // (5,6)

  println(sample.intArray.value.mkString(",")) // 8,9 first, then 1,2,3
  sample.intArray.value = Array(1, 2, 3)

  sample.numberOfCalls.value += 1 // incremented for each execution of TestUserPrefs
  println("number of calls: " + sample.numberOfCalls.value)
}
share|improve this question
    
Could you describe your design goals and requirements? To store and access preferences in a natural way is too vague. For example, why not just use Scala's case classes to represent preferences and serialize them to JSON or YAML? Do you need dynamic preferences (with keys unknown at compile time)? If so, in what way you want them to be type-safe? –  Petr Pudlák Feb 9 '13 at 20:43
    
My design goal is to save some user preferences that can be configured in a dialog and some implicit settings like "last opened files". This should be done in a transparent way - the user of that library wouldn't need to call "save" explicitly and the save location is configured automatically. Also a value that is used as an Int should be loaded as such. But YAML is a good point - maybe a wrapper to snakeyaml (defining setters and getters in a scala-like fashion) would be nice. The YAML-example looks pretty nice! –  michael_s Feb 9 '13 at 22:09
    
That doesn't seem to be very transparent though - example: val bar = config.getInt("simple-lib.bar") - in my example code you could use config.bar directly as Int. –  michael_s Feb 10 '13 at 10:03

1 Answer 1

You had asked if anyone has solved this. Take a look at, Configrity. It has the same functionality, however it doesn't use java.util.prefs.Preferences, to store preferences in. Does have interoperability with java.util.Properties, that's close enough for me.

Also it supports YAML import/export.

I have a feeling the author of Configrity was not thinking of desktop usage.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.