diff --git a/CHANGES.md b/CHANGES.md index 45426da3..22884bf8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,6 +14,7 @@ * [File.nonEmpty](https://github.com/pathikrit/better-files/commit/18c9cd51b7b2e503ff4944050ac5119470869e6e) * [Update metadata API](https://github.com/pathikrit/better-files/commit/c3d65951d80f09b813e158a9e3a1785c622353b3) * [Issue #80](https://github.com/pathikrit/better-files/issues/80): Unzip filters +* [PR #107](https://github.com/pathikrit/better-files/pull/127): Java serialization utils ## v2.17.1 diff --git a/README.md b/README.md index 25007f4e..1b4bc7cd 100644 --- a/README.md +++ b/README.md @@ -17,16 +17,17 @@ 0. [Simple I/O](#file-readwrite) 0. [Streams](#streams) 0. [Encodings](#encodings) + 0. [Java serialization utils](#java-serialization-utils) 0. [Java compatibility](#java-interoperability) 0. [Globbing](#globbing) - 0. [File system operations](#file-system-operations) + 0. [File system operations](#file-system-operations) 0. [Temporary files](#temporary-files) 0. [UNIX DSL](#unix-dsl) 0. [File attributes](#file-attributes) 0. [File comparison](#file-comparison) 0. [Zip/Unzip](#zip-apis) 0. [Automatic Resource Management](#lightweight-arm) - 0. [Scanner] (#scanner) + 0. [Scanner](#scanner) 0. [File Monitoring](#file-monitoring) 0. [Reactive File Watcher](#akka-file-watcher) @@ -207,6 +208,27 @@ If you also wish to write BOMs while encoding, you would need to supply it as: ```scala file.write("hello world")(charset = UnicodeCharset("UTF-8", writeByteOrderMarkers = true)) ``` + +### Java serialization utils + +Some common utils to serialize/deserialize using Java's serialization util +```scala +case class Person(name: String, age: Int) +val person = new Person("Chris", 24) + +// Write +file.newOutputStream.buffered.asObjectOutputStream.serialize(obj).flush() + +// Read +val person2 = file.newInputStream.buffered.asObjectInputStream.readObject().asInstanceOf[Person] +assert(person == person2) +``` + +The above can be simply written as: +```scala +val person2: Person = file.writeSerialized(person).readDeserialized[Person] +assert(person == person2) +``` ### Java interoperability You can always access the Java I/O classes: diff --git a/core/src/main/scala/better/files/File.scala b/core/src/main/scala/better/files/File.scala index 76d24a95..7ae171b5 100644 --- a/core/src/main/scala/better/files/File.scala +++ b/core/src/main/scala/better/files/File.scala @@ -320,6 +320,7 @@ class File private(val path: Path) { def inputStream(implicit openOptions: File.OpenOptions = File.OpenOptions.default): ManagedResource[InputStream] = newInputStream(openOptions).autoClosed + //TODO: Move this to inputstream implicit def newDigestInputStream(digest: MessageDigest)(implicit openOptions: File.OpenOptions = File.OpenOptions.default): DigestInputStream = new DigestInputStream(newInputStream(openOptions), digest) @@ -362,6 +363,25 @@ class File private(val path: Path) { def watchService: ManagedResource[WatchService] = newWatchService.autoClosed + /** + * Serialize a object using Java's serializer into this file + * + * @param obj + * @return + */ + def writeSerialized(obj: Serializable)(implicit openOptions: File.OpenOptions = File.OpenOptions.default): this.type = { + createIfNotExists().outputStream(openOptions).foreach(_.buffered.asObjectOutputStream.serialize(obj).flush()) + this + } + + /** + * Deserialize a object using Java's default serialization from this file + * + * @return + */ + def readDeserialized[A](implicit openOptions: File.OpenOptions = File.OpenOptions.default): A = + inputStream(openOptions).map(_.buffered.asObjectInputStream.readObject().asInstanceOf[A]).head + def register(service: WatchService, events: File.Events = File.Events.all): this.type = { path.register(service, events.toArray) this diff --git a/core/src/main/scala/better/files/Implicits.scala b/core/src/main/scala/better/files/Implicits.scala index 5e4e01f7..716cc5ee 100644 --- a/core/src/main/scala/better/files/Implicits.scala +++ b/core/src/main/scala/better/files/Implicits.scala @@ -63,9 +63,15 @@ trait Implicits { def buffered: BufferedInputStream = new BufferedInputStream(in) + def buffered(bufferSize: Int): BufferedInputStream = + new BufferedInputStream(in, bufferSize) + def gzipped: GZIPInputStream = new GZIPInputStream(in) + def asObjectInputStream: ObjectInputStream = + new ObjectInputStream(in) + def reader(implicit charset: Charset = File.defaultCharset): InputStreamReader = new InputStreamReader(in, charset) @@ -97,6 +103,9 @@ trait Implicits { def tee(out2: OutputStream): OutputStream = new TeeOutputStream(out, out2) + + def asObjectOutputStream: ObjectOutputStream = + new ObjectOutputStream(out) } implicit class ReaderOps(reader: Reader) { @@ -130,6 +139,18 @@ trait Implicits { file.collectChildren(child => matcher.matches(child.path))(visitOptions) } + implicit class ObjectInputStreamOps(ois: ObjectInputStream) { + def deserialize[A]: A = + ois.readObject().asInstanceOf[A] + } + + implicit class ObjectOutputStreamOps(val oos: ObjectOutputStream) { + def serialize(obj: Serializable): oos.type = { + oos.writeObject(obj) + oos + } + } + implicit class ZipOutputStreamOps(val out: ZipOutputStream) { /** diff --git a/core/src/main/scala/better/files/package.scala b/core/src/main/scala/better/files/package.scala index 1b3ec8ef..242fc6a1 100644 --- a/core/src/main/scala/better/files/package.scala +++ b/core/src/main/scala/better/files/package.scala @@ -21,7 +21,7 @@ package object files extends Implicits { def resourceAsStream(name: String): InputStream = currentClassLoader().getResourceAsStream(name) - type ManagedResource[A <: Closeable] = Traversable[A] + type ManagedResource[A <: Closeable] = Traversable[A] //TODO: Make this a class so we don't have to call .head // Some utils: private[files] def newMultiMap[A, B]: mutable.MultiMap[A, B] = new mutable.HashMap[A, mutable.Set[B]] with mutable.MultiMap[A, B] diff --git a/core/src/test/scala/better/files/FileSpec.scala b/core/src/test/scala/better/files/FileSpec.scala index 2f04a621..bb38ed6d 100644 --- a/core/src/test/scala/better/files/FileSpec.scala +++ b/core/src/test/scala/better/files/FileSpec.scala @@ -446,4 +446,18 @@ class FileSpec extends CommonSpec { (t2 writeBytes t1.bytes).contentAsString shouldEqual t1.contentAsString } + + it should "serialize/deserialize" in { + class Person(val name: String, val age: Int) extends Serializable + val p1 = new Person("Chris", 34) + + File.usingTemporaryFile() {f => //serialization round-trip test + assert(f.isEmpty) + f.writeSerialized(p1) + assert(f.nonEmpty) + val p2: Person = f.readDeserialized[Person] + assert(p1.name === p2.name) + assert(p1.age === p2.age) + } + } }