summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBurak Emir <emir@epfl.ch>2007-09-17 15:21:46 +0000
committerBurak Emir <emir@epfl.ch>2007-09-17 15:21:46 +0000
commit3f9b82c88d74c1b03daf5131b50c172213f40a63 (patch)
treecc2e9da177d9733f5e6eedc75666140feb9fcf68
parent1c1b5e88fbd2076e64a59d04bbac6823a823bfd0 (diff)
downloadscala-3f9b82c88d74c1b03daf5131b50c172213f40a63.tar.gz
scala-3f9b82c88d74c1b03daf5131b50c172213f40a63.tar.bz2
scala-3f9b82c88d74c1b03daf5131b50c172213f40a63.zip
added classes for persistent storage of XML
-rw-r--r--src/library/scala/xml/persistent/CachedFileStorage.scala109
-rw-r--r--src/library/scala/xml/persistent/Index.scala5
-rw-r--r--src/library/scala/xml/persistent/IndexedStorage.scala37
-rw-r--r--src/library/scala/xml/persistent/SetStorage.scala30
4 files changed, 181 insertions, 0 deletions
diff --git a/src/library/scala/xml/persistent/CachedFileStorage.scala b/src/library/scala/xml/persistent/CachedFileStorage.scala
new file mode 100644
index 0000000000..32d74a7117
--- /dev/null
+++ b/src/library/scala/xml/persistent/CachedFileStorage.scala
@@ -0,0 +1,109 @@
+package scala.xml.persistent
+
+import java.io.{File,FileOutputStream}
+import java.nio.ByteBuffer
+import java.nio.channels.Channels
+
+/** mutable storage of immutable xml trees. Everything is kept in memory,
+ * with a thread periodically checking for changes and writing to file.
+ * To ensure atomicity, two files are used, filename1 and '$'+filename1.
+ * The implementation switches between the two, deleting the older one
+ * after a complete dump of the database has been written.
+ */
+abstract class CachedFileStorage(private val file1: File)
+extends java.lang.Thread with scala.util.logging.Logged {
+
+ private val file2 = new File(file1.getParent, file1.getName+"$")
+
+ /** either equals file1 or file2, references the next file in which updates will be stored
+ */
+ private var theFile: File = null
+
+ private def switch = { theFile = if( theFile == file1 ) file2 else file1; }
+
+ /** this storage modified since last modification check */
+ protected var dirty = false
+
+ /** period between modification checks, in milliseconds */
+ protected val interval = 1000
+
+ /** finds and loads the storage file. subclasses should call this method
+ * prior to any other, but only once, to obtain the initial sequence of nodes.
+ */
+ protected def initialNodes: Iterator[Node] = (file1.exists, file2.exists) match {
+ case (false,false) =>
+ theFile = file1
+ Iterator.empty
+ case (true, true ) if (file1.lastModified < file2.lastModified) =>
+ theFile = file2
+ load
+ case (true, _ ) =>
+ theFile = file1
+ load
+ case _ =>
+ theFile = file2
+ load
+ }
+
+ /** returns an iterator over the nodes in this storage */
+ def nodes: Iterator[Node]
+
+ /** adds a node, setting this.dirty to true as a side effect */
+ def += ( e:Node ): Unit
+
+ /** removes a tree, setting this.dirty to true as a side effect */
+ def -= ( e:Node ): Unit
+
+ /* loads and parses XML from file */
+ private def load: Iterator[Node] = {
+ import scala.io.Source
+ import scala.xml.parsing.ConstructingParser
+ log("[load]\nloading "+theFile)
+ val src = Source.fromFile( theFile )
+ log("parsing "+theFile)
+ val res = ConstructingParser.fromSource(src,false).document.docElem(0)
+ switch
+ log("[load done]")
+ res.child.elements
+ }
+
+ /** saves the XML to file */
+ private def save = if( this.dirty ) {
+ log("[save]\ndeleting "+theFile);
+ theFile.delete();
+ log("creating new "+theFile);
+ theFile.createNewFile();
+ val fos = new FileOutputStream( theFile );
+ val c = fos.getChannel();
+
+ // @todo: optimize
+ val storageNode = <nodes>{ nodes.toList }</nodes>
+ val w = Channels.newWriter(c, "utf-8")
+ XML.write(w, storageNode, "utf-8", true, null)
+
+ log("writing to "+theFile);
+
+ w.close
+ c.close
+ fos.close
+ dirty = false
+ switch
+ log("[save done]")
+ }
+
+ /** run method of the thread. remember to use start() to start a thread, not run. */
+ override def run = {
+ log("[run]\nstarting storage thread, checking every "+interval+" ms");
+ while(true) {
+ Thread.sleep( this.interval );
+ save
+ }
+ }
+
+ /** forces writing of contents to the file, even if there has not been any update. */
+ def flush = {
+ this.dirty = true;
+ save
+ }
+}
+
diff --git a/src/library/scala/xml/persistent/Index.scala b/src/library/scala/xml/persistent/Index.scala
new file mode 100644
index 0000000000..cf7334c5f4
--- /dev/null
+++ b/src/library/scala/xml/persistent/Index.scala
@@ -0,0 +1,5 @@
+package scala.xml.persistent
+
+/** an Index returns some unique key that is part of a node
+ */
+abstract class Index[A] extends Function1[Node,A] {}
diff --git a/src/library/scala/xml/persistent/IndexedStorage.scala b/src/library/scala/xml/persistent/IndexedStorage.scala
new file mode 100644
index 0000000000..5af307a210
--- /dev/null
+++ b/src/library/scala/xml/persistent/IndexedStorage.scala
@@ -0,0 +1,37 @@
+package scala.xml.persistent
+
+import scala.collection.mutable
+import java.io.File
+
+/** indexed multiset of xml trees. The index may be an arbitrary totally
+ * type, especially one can construct indices by selecting parts of
+ * xml nodes.
+ */
+class IndexedStorage[A](file: File, index: Index[A]) //@todo
+extends CachedFileStorage( file ) {
+
+ private var theMap: mutable.Map[A,Node] = new mutable.HashMap[A,Node]()
+
+ super.initialNodes.foreach { x:Node => this += x }
+
+ this.dirty = false
+
+ def += (e: Node): Unit = synchronized {
+ log("added element at index '"+index(e)+"'")
+ dirty = true
+ theMap(index(e)) = e
+ }
+
+ def -= (e: Node): Unit = synchronized {
+ log("removed element at index '"+index(e)+"'")
+ dirty = true
+ theMap -= index( e )
+ }
+
+ def nodes: Iterator[Node] = synchronized {
+ theMap.values
+ }
+
+ def lookup(n: A): Option[Node] = theMap.get(n)
+
+}
diff --git a/src/library/scala/xml/persistent/SetStorage.scala b/src/library/scala/xml/persistent/SetStorage.scala
new file mode 100644
index 0000000000..d094e6514a
--- /dev/null
+++ b/src/library/scala/xml/persistent/SetStorage.scala
@@ -0,0 +1,30 @@
+package scala.xml.persistent
+
+import scala.collection.mutable
+import java.io.File
+/** a persistent store with set semantics. This class allows to add and remove
+ * trees, but never contains two structurally equal trees.
+ */
+class SetStorage(file: File) extends CachedFileStorage(file) {
+
+ private var theSet: mutable.HashSet[Node] = new mutable.HashSet[Node]
+
+ // initialize
+
+ {
+ val it = super.initialNodes
+ dirty = it.hasNext
+ for(val x <- it) {
+ theSet += x;
+ }
+ }
+
+ /* forwarding methods to hashset*/
+
+ def += ( e:Node ): Unit = synchronized { this.dirty = true; theSet += e }
+
+ def -= ( e:Node ): Unit = synchronized { this.dirty = true; theSet -= e }
+
+ def nodes = synchronized { theSet.elements }
+
+}