NIO.2でファイルを扱う

最近ファイル扱う時はbetter-filesを使っている。
better-files自体はJava NIO周りのScala wrapperなんだけど、そういえばJava NIO.2で追加されたファイル操作周りのAPIをあまり知らなかったから使ってみる。

# NIO.2
そもそもNIO.2って？ところで。
Java SE7で導入されたAPIでJava 1.4で導入されたNIOを強化するもの。具体的には以下。

- ファイルシステムインタフェース
- 非同期I/O
- ソケットチャネル機能の完成

NIOに取り込めなかった機能を取り込んでいる。
この後は最初に述べた通りファイル、つまりファイルシステムインタフェースについて見ていきます。

実行環境はこんな感じ。Scalaで書きます。

- Java: `1.8.0_131`
- Scala: `2.12.3`

以下をimportをしていることが前提です。

```scala
scala> import java.nio.file._
scala> import java.io.File
scala> import java.nio.file.attribute._
```

# `java.nio.file.Path`
ファイルやディレクトリの場所を示すインタフェース。
`Path`が今回のファイルシステムインタフェースのキモなんじゃないかと思う。
`File`がディレクトリも表すって違和感あったから、パスって考え方ができたのは良い感じ。

## インスタンス生成

- `java.nio.file.Paths`を使う

```scala
scala> val path1 = Paths.get("dir/file.txt")
path1: java.nio.file.Path = dir/file.txt
```

- `java.nio.file.FileSystems`を使う

```scala
scala> val path2 = FileSystems.getDefault.getPath("dir", "file.txt")
path2: java.nio.file.Path = dir/file.txt
```

- `java.io.File`からの変換

```scala
scala> val file = new File("dir/file.txt")
file: java.io.File = dir/file.txt

scala> val path3 = file.toPath
path3: java.nio.file.Path = dir/file.txt
```

## 絶対パス/相対パス
今までの例は相対パスで書いていた。絶対パスで生成したいなら以下な感じ。

```scala
scala> val absolutePath1 = Paths.get("/tmp/dir/file.txt")
absolutePath1: java.nio.file.Path = /tmp/dir/file.txt

scala> val absolutePath2 = Paths.get("/", "tmp", "dir", "file.txt")
absolutePath2: java.nio.file.Path = /tmp/dir/file.txt
```

## `java.io.File`への変換

```scala
scala> val path = Paths.get("dir/file.txt")
path: java.nio.file.Path = dir/file.txt

scala> val file = path.toFile
file: java.io.File = dir/file.txt
```

# `java.nio.file.FileSystem`
ファイルシステムへのインタフェース。
通常のファイルシステム、ZIPファイル、Jarファイルをこのインタフェースで透過的に扱えるみたい。

# `java.nio.file.FileSystems`
`FileSystem`を生成するファクトリですね。
`Path`を生成する例では`getDefalut`メソッドによりプラットフォームデフォルトのファイルシステムを取得する。

```scala
scala> val file = path1.toFile
file: java.io.File = dir/file.txt
```

# `java.nio.file.Paths`
さっき使ったけど`Path`を生成するメソッドしかないんで、`Path`のファクトリですね。
中で`FileSystems.getDefault.getPath`を呼んでいるだけなので、こっち使うほうが短くて良いですね。

# `java.nio.file.Files`
ファイルとかディレクトリを扱うインタフェース。
基本的には`Path`を渡して、そのPathの実体のファイルとかディクレ取りに対して何かをやる感じ。
commons IOの`FileUtils`に似ている。

きっと使う頻度が多いcopy/move/deleteを使ってみる。

## copy

```scala
scala> val src = Paths.get("dir/src.txt")
src: java.nio.file.Path = dir/src.txt

scala> val dest = Paths.get("dir/dest.txt")
dest: java.nio.file.Path = dir/dest.txt

scala> val path = Files.copy(src, dest)
path: java.nio.file.Path = dir/dest.txt
```

戻り値は引数で渡したdestをそのまま返します。
srcのファイルが存在しない場合は`NoSuchFileException`が発生する。
destのファイルが存在している場合は`FileAlreadyExistsException`が発生する。

`copy`メソッドには第3引数で`CopyOption`というインタフェースを受け取れます。これでどういう感じでコピーするかを決めることができます。
例えば`StandardCopyOption.REPLACE_EXISTING`でファイルが存在している場合は置換するって指定ができます。
詳しくは以下を見ると良いです。

- [列挙StandardCopyOption](https://docs.oracle.com/javase/jp/8/docs/api/java/nio/file/StandardCopyOption.html)
- [列挙LinkOption](https://docs.oracle.com/javase/jp/8/docs/api/java/nio/file/LinkOption.html)

## move

```scala
scala> val src = Paths.get("dir/src.txt")
src: java.nio.file.Path = dir/src.txt

scala> val path = Files.move(src, dest)
path: java.nio.file.Path = dir/dest.txt
```

ほぼ`copy`と一緒ですね。

destファイルが存在する場合は`FileAlreadyExistsException`が発生します。

## delete

```scala
scala> val path = Paths.get("dir/file.txt")
path: java.nio.file.Path = dir/file.txt

scala> Files.delete(path)
```

`delete`の場合は戻り値がないです。これは`Path`を削除したら存在しないモノになるから何も返さないんでしょうね。

削除対象のファイルが存在しない場合は`NoSuchFileException`が発生します。

ここではファイルで試したけど、ディレクトリももちろん扱えます。

`Path`を`Files`に渡して操作するAPIはいけている。
他にも`Files`からリーダー、ライター、チャネル、ストリームを取得できるメソッドがあって、`File`よりも扱いやすくなっている。

# ファイル属性
`File`の場合はOS共通の属性しか扱えなかったけど、NIO.2ではOS毎の属性も扱えるようになっている。
属性とはファイルのオーナー、作成日、更新日、権限とか。

実際に取得してみる。

```scala
scala> val path = Paths.get("dir/file.txt")
path: java.nio.file.Path = dir/file.txt

scala> val attributes = Files.getFileAttributeView(path, classOf[PosixFileAttributeView]).readAttributes()
attributes: java.nio.file.attribute.PosixFileAttributes = sun.nio.fs.UnixFileAttributes@364beb26


scala> attributes.creationTime
res0: java.nio.file.attribute.FileTime = 2017-08-28T22:45:16Z

scala> attributes.owner
res1: java.nio.file.attribute.UserPrincipal = asmasa
```

設定ももちろんできる。

```scala
scala> Files.setLastModifiedTime(path, FileTime.fromMillis(System.currentTimeMillis()))
res2: java.nio.file.Path = dir/file.txt

scala> val owner = FileSystems.getDefault.getUserPrincipalLookupService .lookupPrincipalByName("test")
owner: java.nio.file.attribute.UserPrincipal = test

scala> Files.setOwner(path, owner)
res3: java.nio.file.Path = dir/file.txt
```

こんな感じでもできる。

```scala
scala> Files.setAttribute(path, "lastModifiedTime", FileTime.fromMillis(System.currentTimeMillis()))
res4: java.nio.file.Path = dir/file.txt
```

今更ながらNIO.2のファイルインタフェースを見てみたけど、`File`の時と比べると使いやすくなっていますね。
`Files`を通して操作をするっていうのがわかりやすくて良いです。