メモ : 【Java】監視しているディレクトリにファイルが入ったら処理を行う

#**背景**
前回、[CSVファイルを加工して出力する](https://qiita.com/tedkuma/items/6d4342232ca4433d2566)というのを書いたんですが、
対象のディレクトリにCSVファイルが入ってきたらファイルを加工するっていう流れにしたいなー
と思ったので、コードを書き残します。

![監視.png](https://qiita-image-store.s3.amazonaws.com/0/237418/15010d0d-40ed-c157-ec74-f929e39d5ed0.png)

#**まずはディレクトリの監視**
-　**[WatchService 使い方メモ](https://qiita.com/opengl-8080/items/91a8ac36ab3d8600a529)**
-　**[Javaでファイルが作成されるのを待って、作成を検知してから次の処理を行う](http://blog.codebook-10000.com/entry/java-file-create-watching)**
 :grinning: こちらのページを参考にさせて頂きました。ありがとうございます。

```test.java
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.Watchable;

import static java.nio.file.StandardWatchEventKinds.*;
import static java.nio.file.WatchEvent.*;

public class test {

    public static void main(String[] args) throws Exception {
        Path dir = Paths.get("C://develop//BOX");
        WatchService watcher = FileSystems.getDefault().newWatchService();
        dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);

        for (;;) {
            WatchKey watchKey = watcher.take();
            for (WatchEvent<?> event: watchKey.pollEvents()) {
                if (event.kind() == OVERFLOW) continue;

                WatchEvent<Path> ev = cast(event);
                Path name = ev.context();
                Path child = dir.resolve(name);
                System.out.format("%s: %s\n", event.kind().name(), child);
            }
            watchKey.reset();
        }
    }

    @SuppressWarnings("unchecked")
    static <T> WatchEvent<T> cast(WatchEvent<?> event) {
        return (WatchEvent<T>)event;
    }
}
```
では、**C:\develop** に **BOX** というディレクトリを作成して、
![1.png](https://qiita-image-store.s3.amazonaws.com/0/237418/ba7193bb-6b52-279a-81e3-241ac1aa93c6.png)


任意の場所で test.java をコンパイルしてクラスを実行してみます。( ※ 今回ワタシは D:\で実行してます)
![2.png](https://qiita-image-store.s3.amazonaws.com/0/237418/1a8d363f-0ed6-df1c-1499-a5be94b78074.png)

わーっ！コンパイル時にエラーが出ちゃったよって方は :beginner:**[こちら](http://www.javaroad.jp/faq/faq_error1.htm)** 参考になります。

では、classファイルを実行します。適当なファイルをBOXディレクトリに入れてみます。
![3.png](https://qiita-image-store.s3.amazonaws.com/0/237418/356f2354-e17a-d2da-8c47-a6b5f76746fa.png)

おーっ‼ディレクトリに入れた さんぷる.txt が
 ENTRY_CREATE(作成) と ENTRY_MODIFY(変更) と表示されました。
![4.png](https://qiita-image-store.s3.amazonaws.com/0/237418/8b6266c7-b63c-14df-e5ed-a17e924f37ab.png)

ファイルをディレクトリから削除するとENTRY_DELETE(削除)が表示されています。
![5.png](https://qiita-image-store.s3.amazonaws.com/0/237418/0ad8a17c-e6ae-bee8-8351-8ad1ac75ac49.png)

実際動かしてからの方がわかるなーと思うので先に動かした時の画像貼りましたが
test.javaにはこんなのが書いてます。(コメントはワタシの解釈で書いてるので間違えてるかも…。)
2行目の **WatchService watcher = FileSystems.getDefault().newWatchService();** というのは
決まった言い回しらしいので深く考えずそのまま使います。

```java
Path dir = Paths.get("C://develop//BOX");                            //"dir"に監視対象のディレクトリを指定して
WatchService watcher = FileSystems.getDefault().newWatchService();   //WatchServiceを"watcher"という名前で作成
dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);     //"dir"内で監視するイベントを指定(作成,削除,変更)
```

```java
for (;;) {                                                           //イベントを待機するために無限ループを実装
   WatchKey watchKey = watcher.take();                               //watcherから監視キー"watchKey"を取得する
   for (WatchEvent<?> event: watchKey.pollEvents()) {                //pollEventsメソッドでWatchEventが格納されたListが取得出来る。
       if (event.kind() == OVERFLOW) continue;                       //"OVERFLOW"はイベントが消失or破棄されたのを示す特殊な監視イベント

        WatchEvent<Path> ev = cast(event);
        Path name = ev.context();                                    //"name"にイベント・コンテキストで取得してファイル名を設定　　　　　　　
        Path child = dir.resolve(name);                              //resolveメソッドでフルパス("dir"のパスに"name"を追加したもの)を設定
        System.out.format("%s: %s\n", event.kind().name(), child);   //出力時は【イベント名:ファイルのフルパス】で出力する指示
   }
   watchKey.reset();                                                 //監視キーをリセットする(WatchKeyの状態をREADYに戻すため)
```

#**csvを加工する処理を組み合わせます**
<details><summary>前回のコード　[ 読み込んだcsvを加工(条件で抽出、変更)して出力する ]</summary>
<div>

```test.java
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class test{

    public static void main(String[] args) {
        process("20180712.csv",  ":00,","output.csv");
    }

    public static void process(String read_file, String searchString, String output_file){

        try(FileReader fr = new FileReader(read_file);BufferedReader br = new BufferedReader(fr);
            FileWriter fw = new FileWriter(output_file);BufferedWriter bw = new BufferedWriter(fw)){

            String line;
            while ((line = br.readLine()) != null) {
                Pattern p = Pattern.compile(searchString);
                Matcher m = p.matcher(line);
                if (m.find()){
                    String[] csvArray;
                    csvArray = line.split(",");
                    String time  = csvArray[0];
                    float press = Float.parseFloat(csvArray[1])%10000;
                    String pressure = String.format("%.2f", press);
                    String temperature  = csvArray[2];
                    String outputLine = String.join(",",time,pressure,temperature);
                    bw.write(outputLine);
                    bw.newLine();
                }else{ 
                }
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}
```
</div></details>

前回のコードと上で書いたコードは大きく変えてません。ほぼ結合しただけです。

```test.java
import java.nio.file.FileSystems;      //ディレクトリ監視に必要
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.Watchable;
import static java.nio.file.StandardWatchEventKinds.*;
import static java.nio.file.WatchEvent.*;

import java.io.BufferedReader;        //csvを加工して出力するのに必要
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class test {

    public static void main(String[] args) throws Exception {
        
        Path dir = Paths.get(System.getenv("CSV_DIR"));       //今回は 監視するディレクトリを環境変数"CSV_DIR"に設定
        WatchService watcher = FileSystems.getDefault().newWatchService();
        dir.register(watcher, ENTRY_CREATE);                  //今回は ENTRY_CREATEだけ検知すればOK
   
        for (;;) {
            WatchKey watchKey = watcher.take();
            for (WatchEvent<?> event: watchKey.pollEvents()) {
                if (event.kind() == OVERFLOW) continue;              
                WatchEvent<Path> ev = cast(event);
                Path name = ev.context();
                Path child = dir.resolve(name);
                String newfile =String.format("%s", child);

                process(newfile, ":00,","D://tedkuma//★"+name); //processで読込むファイルに検知したファイルのフルパスを渡す
                System.out.println("Output success:"+name);
            }
            watchKey.reset();
        }  
    }

    @SuppressWarnings("unchecked")
    static <T> WatchEvent<T> cast(WatchEvent<?> event) {
        return (WatchEvent<T>)event;
    }

    public static void process(String read_file, String searchString, String output_file){ //以下は前回のをそのまま流用

        try(FileReader fr = new FileReader(read_file);BufferedReader br = new BufferedReader(fr);
            FileWriter fw = new FileWriter(output_file);BufferedWriter bw = new BufferedWriter(fw)){

            String line;
            while ((line = br.readLine()) != null) {
                Pattern p = Pattern.compile(searchString);
                Matcher m = p.matcher(line);
                if (m.find()){
                    String[] csvArray;
                    csvArray = line.split(",");
                    String time  = csvArray[0];
                    float press = Float.parseFloat(csvArray[1])%10000;
                    String pressure = String.format("%.2f", press);
                    String temperature  = csvArray[2];
                    String outputLine = String.join(",",time,pressure,temperature);
                    bw.write(outputLine);
                    bw.newLine();
                }else{ 
                }
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}
```


日本語のコメントとか★マークとかがjavaファイルの中に含まれているので
javacだけではコンパイルエラーになってしまいます  :sweat:
:skull:　エラー: 　この文字は、エンコーディングMS932にマップできません　って出てしまうので、
今回は   **javac -encodhing utf-8 test.java** でコンパイルしています。では、実行してみます。
![11.png](https://qiita-image-store.s3.amazonaws.com/0/237418/ee955226-b23e-b203-9cfa-a1af15229045.png)

環境変数"CSV_DIR"は C:\develop\monitored という場所に設定しました。(今回の監視対象ディレクトリ)
この中に加工したいCSVファイルを入れてみます。
![12.png](https://qiita-image-store.s3.amazonaws.com/0/237418/656b34e7-895b-d271-e3f1-741d3ca535ac.png)

Output success:の後ろにディレクトリに入れたファイル名が表示されています。
![13.png](https://qiita-image-store.s3.amazonaws.com/0/237418/2c438975-0dbe-f982-5c90-78b63e2cea25.png)

出力先に指定したDドライブのtedkumaディレクトリを確認してみます。
ファイル名の頭に★が付いたファイルが出力されて、中のcsvも加工されています。
![14.png](https://qiita-image-store.s3.amazonaws.com/0/237418/102cb524-e0c9-11ad-193a-b1782eeb140b.png)

今回は以上です。