Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

I/O utils for Java serialization #127

Merged
merged 4 commits into from
Mar 15, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
26 changes: 24 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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:
Expand Down
20 changes: 20 additions & 0 deletions core/src/main/scala/better/files/File.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand Down
21 changes: 21 additions & 0 deletions core/src/main/scala/better/files/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {

/**
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala/better/files/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
14 changes: 14 additions & 0 deletions core/src/test/scala/better/files/FileSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}