From ca1abed3a9f09162f91288d0fc0d67b9c8a90512 Mon Sep 17 00:00:00 2001 From: Evgeny Gerashchenko Date: Fri, 27 Jan 2012 18:30:18 +0400 Subject: [PATCH] Added example-vfs project written on Kotlin. --- .idea/modules.xml | 1 + examples/example-vfs/example-vfs.iml | 13 ++ examples/example-vfs/example-vfs.ipr | 170 +++++++++++++++++++ examples/example-vfs/src/FileSystem.kt | 130 ++++++++++++++ examples/example-vfs/src/Main.kt | 36 ++++ examples/example-vfs/src/RefreshQueue.kt | 138 +++++++++++++++ examples/example-vfs/src/Utils.kt | 42 +++++ examples/example-vfs/src/VirtualFile.kt | 164 ++++++++++++++++++ examples/example-vfs/src/VirtualFileEvent.kt | 42 +++++ 9 files changed, 736 insertions(+) create mode 100644 examples/example-vfs/example-vfs.iml create mode 100644 examples/example-vfs/example-vfs.ipr create mode 100644 examples/example-vfs/src/FileSystem.kt create mode 100644 examples/example-vfs/src/Main.kt create mode 100644 examples/example-vfs/src/RefreshQueue.kt create mode 100644 examples/example-vfs/src/Utils.kt create mode 100644 examples/example-vfs/src/VirtualFile.kt create mode 100644 examples/example-vfs/src/VirtualFileEvent.kt diff --git a/.idea/modules.xml b/.idea/modules.xml index 36ef0191ae1..c1f4844a4d1 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -7,6 +7,7 @@ + diff --git a/examples/example-vfs/example-vfs.iml b/examples/example-vfs/example-vfs.iml new file mode 100644 index 00000000000..37bed8abc1d --- /dev/null +++ b/examples/example-vfs/example-vfs.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/examples/example-vfs/example-vfs.ipr b/examples/example-vfs/example-vfs.ipr new file mode 100644 index 00000000000..77f63b1e9c3 --- /dev/null +++ b/examples/example-vfs/example-vfs.ipr @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + http://www.w3.org/1999/xhtml + + + + + + + + + diff --git a/examples/example-vfs/src/FileSystem.kt b/examples/example-vfs/src/FileSystem.kt new file mode 100644 index 00000000000..e9380227794 --- /dev/null +++ b/examples/example-vfs/src/FileSystem.kt @@ -0,0 +1,130 @@ +package org.jetbrains.jet.samples.vfs; + +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import org.jetbrains.jet.samples.vfs.utils.*; +import java.util.concurrent.locks.Lock +import java.io.File +import java.util.List +import java.util.ArrayList +import java.util.HashMap +import java.util.Arrays +import java.util.Map + +import std.util.* + +/** + * File system singleton. To work with virtual file system, read/write locks should be + * acquired: use read() and write() higher-order functions. + */ +public object FileSystem { + private val lock = ReentrantReadWriteLock() + internal val watchedDirectories = ArrayList + + // FIXME VirtualFiles should be used as hashmap keys themselves, + // but overriden hashCode() method fails in runtime with ClassCastException (KT-1134) + /** + * Mapping from virtual files to metainformation + */ + internal val fileToInfo = HashMap() + private val listeners = ArrayList() + + /** + * Returns corresponding virtual file for java.io.File. + * + * @param ioFile file + */ + public fun getFileByIoFile(ioFile : File) : VirtualFile { + FileSystem.assertCanRead() + return PhysicalVirtualFile(ioFile.getAbsolutePath().sure().toSystemIndependentPath()) + } + + /** + * Returns virtual file for path to it. + * + * @param path path to file + */ + public fun getFileByPath(path : String) : VirtualFile { + return getFileByIoFile(File(path)) + } + + /** + * Runs function with read lock. + */ + public inline fun read(task : () -> T) : T { + return locked(lock.readLock().sure(), task) + } + + /** + * Runs function with write lock. + */ + public inline fun write(task : () -> T) : T { + return locked(lock.writeLock().sure(), task) + } + + /** + * Adds directory to list of watched directories. Watched directories are + * periodically refreshed and corresponding events are sent to file system listeners. + * + * @param dir directory which should be watched + */ + public fun addWatchedDirectory(dir : VirtualFile) { + assertCanRead() + if (dir.isDirectory()) { + watchedDirectories.add(dir) + scanAndAddRecursivelyNoEvents(dir) + + RefreshQueue.scheduleRefresh() + } + } + + /* Scans file recursively and adds info about it to fileToInfo map */ + private fun scanAndAddRecursivelyNoEvents(file : VirtualFile) { + assert(FileSystem.fileToInfo[file.path] == null) + + val fileInfo = VirtualFileInfo(file) + FileSystem.fileToInfo[file.path] = fileInfo + fileInfo.children.foreach{ scanAndAddRecursivelyNoEvents(it) } + } + + internal inline fun assertCanRead() { + assert(lock.getReadHoldCount() != 0 || lock.isWriteLockedByCurrentThread()) + } + + internal inline fun assertCanWrite() { + assert(lock.isWriteLockedByCurrentThread()) + } + + /** + * Adds file system listener which should be notified about changing of file system. + */ + public fun addVirtualFileListener(listener : VirtualFileListener) { + listeners.add(listener) + } + + /** + * Removes file system listener. + */ + public fun removeVirtualFileListener(listener : VirtualFileListener) { + listeners.remove(listener) + } + + /* Notifies all listeners */ + internal fun notifyEventHappened(event : VirtualFileEvent) { + for (listener in listeners) { + listener.eventHappened(event) + } + } +} + +private class VirtualFileInfo(file : VirtualFile) { + /* Last modification time */ + var lastModified : Long = 0 + /* List of known children */ + val children : List = ArrayList; + + { + children.addAll(file.children()) + lastModified = file.modificationTime() + } +} diff --git a/examples/example-vfs/src/Main.kt b/examples/example-vfs/src/Main.kt new file mode 100644 index 00000000000..7c288a228b2 --- /dev/null +++ b/examples/example-vfs/src/Main.kt @@ -0,0 +1,36 @@ +package org.jetbrains.jet.samples.vfs.sandbox; + +import org.jetbrains.jet.samples.vfs.*; +import java.io.InputStreamReader +import java.util.Scanner + +/** + * Receives list of directories which should be watched, adds them to virtual file system, + * adds virtual file system listener and waits for 1 minutes, printing out all received event. + */ +fun main(args : Array) { + if (args.size == 0) { + println("Provide list of watched directories as command line arguments") + return + } + + // add watched directory + FileSystem.write { + for (val arg in args) { + FileSystem.addWatchedDirectory(FileSystem.getFileByPath(arg)) + } + } + + // add listener which prints out everything + FileSystem.addVirtualFileListener(object : VirtualFileListener { + override fun eventHappened(event: VirtualFileEvent) { + println(event) + if (event is VirtualFileChangedEvent) { + println("new file size is ${event.file.size()}") + } + } + }) + + // wait for 1 minute + Thread.sleep(60000) +} \ No newline at end of file diff --git a/examples/example-vfs/src/RefreshQueue.kt b/examples/example-vfs/src/RefreshQueue.kt new file mode 100644 index 00000000000..09e7f21713f --- /dev/null +++ b/examples/example-vfs/src/RefreshQueue.kt @@ -0,0 +1,138 @@ +package org.jetbrains.jet.samples.vfs; + +import java.util.concurrent.ConcurrentLinkedQueue +import java.util.List +import java.util.concurrent.locks.ReentrantLock +import java.util.concurrent.LinkedBlockingQueue +import java.util.ArrayList + +import std.util.* +import java.util.Timer +import java.util.TimerTask + +import org.jetbrains.jet.samples.vfs.utils.* + +/** + * Singleton which creates thread for periodically checking if there are changes in + * file system and notifying file system listeners. + */ +internal object RefreshQueue : Runnable { + private val taskQueue = LinkedBlockingQueue>() + private val workerThread: Thread = Thread(this) + private val fullRefreshScheduler = Timer(true); + + { + workerThread.setDaemon(true) + workerThread.start() + + fullRefreshScheduler.scheduleAtFixedRate(createTimerTask { + scheduleRefresh(FileSystem.watchedDirectories) + }, 0, 5000) + } + + private fun takeAndRefreshFiles() { + refreshFiles(taskQueue.take()) + } + + /* Acquires write lock and refreshes file system */ + private fun refreshFiles(files : List) { + FileSystem.write { + files.foreach{ refreshFile(it) } + } + } + + /* Checks for changes in virtual file recursively, notifying listeners. */ + private fun refreshFile(file : VirtualFile) { + FileSystem.assertCanWrite() + if (file.isDirectory()) { + val fileToInfo = FileSystem.fileToInfo + val fileInfo = fileToInfo[file.path] + val oldChildren = fileInfo.children + val newChildren = file.children() + + val addedChildren = listDifference(newChildren, oldChildren) + val deletedChildren = listDifference(oldChildren, newChildren) + val commonChildren = listIntersection(oldChildren, newChildren) + + addedChildren.foreach{ addRecursively(it) } + deletedChildren.foreach{ deleteRecursively(it) } + + fileInfo.children.clear() + fileInfo.children.addAll(newChildren) + + commonChildren.foreach{ refreshFile(it) } + } else { + val fileInfo = FileSystem.fileToInfo[file.path] + assert(fileInfo != null) + + val newModificationTime = file.modificationTime() + if (fileInfo.lastModified != newModificationTime) { + fileInfo.lastModified = newModificationTime + + FileSystem.notifyEventHappened(VirtualFileChangedEvent(file)) + } + } + } + + /* Adds file to file system recursively, notifying listeners */ + private fun addRecursively(file : VirtualFile) { + assert(FileSystem.fileToInfo[file] == null) + + val fileInfo = VirtualFileInfo(file) + + FileSystem.fileToInfo[file.path] = fileInfo + FileSystem.notifyEventHappened(VirtualFileCreateEvent(file)) + + fileInfo.children.foreach{ addRecursively(it) } + } + + /* Deletes file from file system recursively, notifying listeners */ + private fun deleteRecursively(file : VirtualFile) { + val fileInfoMaybe : VirtualFileInfo? = FileSystem.fileToInfo[file.path] + val fileInfo = fileInfoMaybe.sure() + fileInfo.children.foreach{ deleteRecursively(it) } + FileSystem.notifyEventHappened(VirtualFileDeletedEvent(file)) + FileSystem.fileToInfo.remove(file) + } + + /** + * Schedules refresh for given list of files. + */ + public fun scheduleRefresh(files : List) { + taskQueue.put(files) + } + + /** + * Schedules refresh for given list of files. + */ + public fun scheduleRefresh(vararg files : VirtualFile) { + // FIXME This could be written more concise, using map() & toList() (KT-1164 & KT-1172) + val filesList = ArrayList() + files.foreach{ filesList.add(it) } + taskQueue.put(filesList) + + // taskQueue.put(ArrayList(files.map{ it })) + // taskQueue.put(files.map{ it }.toList()) + } + + // FIXME RefreshQueue implements Runnable because of error on accessing + // private fields and methods from anonymous class (KT-1157 & KT-1159) ( + override fun run() { + while (!workerThread.isInterrupted()) { + try { + takeAndRefreshFiles() + } catch (e : InterruptedException) { + } + } + } +} + +// FIXME this method is used because of error on accessing +// private methods from anonymous class (KT-1157) ( +private fun createTimerTask(task : () -> Unit) : TimerTask { + return object : TimerTask() { + override fun run() { + task() + } + } +} \ No newline at end of file diff --git a/examples/example-vfs/src/Utils.kt b/examples/example-vfs/src/Utils.kt new file mode 100644 index 00000000000..7926599c927 --- /dev/null +++ b/examples/example-vfs/src/Utils.kt @@ -0,0 +1,42 @@ +package org.jetbrains.jet.samples.vfs.utils; + +import java.util.concurrent.locks.Lock +import java.util.List +import std.util.* + +/** + * Executes task with given lock acquired. + */ +private fun locked(lock : Lock, body : () -> T) : T { + lock.lock() + try { + return body() + } finally { + lock.unlock(); + } +} + +/** + * Checks boolean value which should be true. If it is false, throws Assertion Error + * + * @param value value to be checked + */ +public fun assert(value : Boolean) { + if (!value) { + throw AssertionError() + } +} + +/** + * Returns list containing all elements which first contains and second does not. + */ +public fun listDifference(first : List, second : List) : List { + return first.filter{ !second.contains(it) }.toList() +} + +/** + * Returns list containing all elements which both lists contain. + */ +public fun listIntersection(first : List, second : List) : List { + return first.filter{ second.contains(it) }.toList() +} diff --git a/examples/example-vfs/src/VirtualFile.kt b/examples/example-vfs/src/VirtualFile.kt new file mode 100644 index 00000000000..f848cedf4c7 --- /dev/null +++ b/examples/example-vfs/src/VirtualFile.kt @@ -0,0 +1,164 @@ +package org.jetbrains.jet.samples.vfs; + +import org.jetbrains.jet.samples.vfs.utils.*; +import java.io.File +import java.io.InputStream +import java.io.FileInputStream +import java.util.ArrayList +import java.util.List + +import std.util.* + +/** + * Abstract virtual file. + */ +public abstract class VirtualFile(public val path : String) { + // FIXME this method should be replaced with val (KT-1168, KT-1170) + protected abstract fun kind() : String + + // FIXME these abstract methods should be replaced with vals (KT-1165) + /** + * Returns file size. + */ + public abstract fun size() : Long + /** + * Returns the time that the virtual file was last modified (milliseconds since + * the epoch (00:00:00 GMT, January 1, 1970). + */ + public abstract fun modificationTime() : Long + /** + * Returns if virtual file exists. + */ + public abstract fun exists() : Boolean + /** + * Returns if virtual file is directory + */ + public abstract fun isDirectory() : Boolean + /** + * Returns list of virtual files which are children for this. + */ + public abstract fun children() : List + + /** + * Opens input stream for reading. After reading, stream should be closed. + * Reading from stream should be performed with read lock acquired. + */ + public abstract fun openInputStream() : InputStream + + fun equals(other : Any?) : Boolean { + return other is VirtualFile && kind() == other.kind() && path == other.path + } + + fun hashCode() : Int { + // FIXME rewrite without casting when it will be possible + return (kind() as java.lang.String).hashCode() * 31 + (path as java.lang.String).hashCode() + } + + fun toString(): String { + return "${kind()}[path=$path]" + } +} + +/** + * Type of virtual file which corresponds to real file in file system of OS. + */ +public class PhysicalVirtualFile(path : String) : VirtualFile(path) { + override fun kind() : String = "Physical" + + private val ioFile : File + get() = File(this.path.toSystemDependentPath()) + + override fun exists(): Boolean { + FileSystem.assertCanRead() + return ioFile.exists() + } + + override fun size(): Long { + FileSystem.assertCanRead() + return ioFile.length() + } + + override fun modificationTime(): Long { + FileSystem.assertCanRead() + return ioFile.lastModified() + } + + override fun isDirectory(): Boolean { + FileSystem.assertCanRead() + return ioFile.isDirectory() + } + + override fun children(): List { + FileSystem.assertCanRead() + return (ioFile.listFiles() ?: Array(0)). + map{ FileSystem.getFileByIoFile(it.sure()) }?.toList() + } + + override fun openInputStream(): InputStream { + FileSystem.assertCanRead() + if (isDirectory()) { + throw IllegalArgumentException("Can't open directory for reading"); + } + return CheckedInputStream(FileInputStream(ioFile)) + } +} + +private fun String.toSystemDependentPath() : String { + // FIXME constants should be extracted (NPE in compiler, KT-1111) + return this.replaceAll("/", java.io.File.separator.sure()) +} + +private fun String.toSystemIndependentPath() : String { + // FIXME constants should be extracted (NPE in compiler, KT-1111) + return this.replaceAll(java.io.File.separator.sure(), "/") +} + +/** + * InputStream wrapper which checks that file system read lock is acquired on each operation. + */ +private class CheckedInputStream(private val wrapped : InputStream) : InputStream() { + override fun read(): Int { + FileSystem.assertCanRead() + return wrapped.read() + } + + override fun read(b: ByteArray?, off: Int, len: Int) : Int { + FileSystem.assertCanRead() + return wrapped.read(b, off, len) + } + + override fun markSupported(): Boolean { + FileSystem.assertCanRead() + return wrapped.markSupported() + } + + override fun skip(n: Long): Long { + FileSystem.assertCanRead() + return wrapped.skip(n) + } + + override fun close() { + FileSystem.assertCanRead() + return wrapped.close() + } + + override fun mark(readlimit: Int) { + FileSystem.assertCanRead() + return wrapped.mark(readlimit) + } + + override fun read(b: ByteArray?): Int { + FileSystem.assertCanRead() + return wrapped.read(b) + } + + override fun reset() { + FileSystem.assertCanRead() + return wrapped.reset() + } + + override fun available(): Int { + FileSystem.assertCanRead() + return wrapped.available() + } +} \ No newline at end of file diff --git a/examples/example-vfs/src/VirtualFileEvent.kt b/examples/example-vfs/src/VirtualFileEvent.kt new file mode 100644 index 00000000000..7f3d043a5c4 --- /dev/null +++ b/examples/example-vfs/src/VirtualFileEvent.kt @@ -0,0 +1,42 @@ +package org.jetbrains.jet.samples.vfs; + +/** + * Listener for receiving virtual file event + */ +public trait VirtualFileListener : java.util.EventListener { + fun eventHappened(val event : VirtualFileEvent) +} + +/** + * Base type of virtual file events + * @property file affected by event + */ +public abstract class VirtualFileEvent(val file : VirtualFile) : Object() { +} + +/** + * Event of creating file + */ +public class VirtualFileCreateEvent(file : VirtualFile) : VirtualFileEvent(file) { + override fun toString(): String? { + return "created ${file}" + } +} + +/** + * Event of deleting file + */ +public class VirtualFileDeletedEvent(file : VirtualFile) : VirtualFileEvent(file) { + override fun toString(): String? { + return "deleted ${file}" + } +} + +/** + * Event of changing file contents + */ +public class VirtualFileChangedEvent(file : VirtualFile) : VirtualFileEvent(file) { + override fun toString(): String? { + return "changed ${file}" + } +}