Embulk - ソースコードリーディング No.3

バッチ処理データ連携ツールのEmbulkのソースコードリーディングを続けます

# 今日のリーディング範囲

前回と同様にguessコマンドのリーディングを続けます
今回からはJava側のリソースになります

 java -jar embulk.jar guess ./try1/example.yml -o config.yml

# リーディング内容

### Runner.java

https://github.com/embulk/embulk/blob/master/embulk-core/src/main/java/org/embulk/command/Runner.java

まずコンストラクタですが、今回はinjector変数が必要になってきます

```java:Runner.java#52
this.service = new EmbulkService(systemConfig);
this.injector = service.getInjector();
```

このinjectorとは何？という話ですが、これはEmbulkService.javaを見るとわかります

https://github.com/embulk/embulk/blob/master/embulk-core/src/main/java/org/embulk/EmbulkService.java

```java:EmbulkService.java#18
public EmbulkService(ConfigSource systemConfig)
{
    ImmutableList.Builder<Module> modules = ImmutableList.builder();
    modules.add(new SystemConfigModule(systemConfig));
    modules.add(new ExecModule());
    modules.add(new ExtensionServiceLoaderModule(systemConfig));
    modules.add(new BuiltinPluginSourceModule());
    modules.add(new JRubyScriptingModule(systemConfig));
    modules.addAll(getAdditionalModules(systemConfig));
    injector = Guice.createInjector(modules.build());
}
```

ここではgoogleが作成したライブラリのGuiceを使っています

GuiceはDIコンテナの一つのようですね
ImmutableListで追加したクラスをGuice#createInjectorで依存性の注入を行い、後で利用するといったものです
実際どう使うかはこの後にわかりやすい例が出てきます

このプログラミングモデル、各モジュールを疎結合にするには非常にいいコーディングですね
違うExec方法を開発したらExecModuleだけ違うものを注入すればいいのかなあと

本筋に戻り、guessの続きを追います
guessは下記メソッドで実行されます

```java:Runner.java#86
public void guess(String partialConfigPath)
{
    ConfigSource config = loadYamlConfig(partialConfigPath);
    checkNextConfigOutputPath(options.getNextConfigOutputPath());

    ExecSession exec = newExecSession(config);
    GuessExecutor guess = injector.getInstance(GuessExecutor.class);
    NextConfig nextConfig = guess.guess(exec, config);

    String yml = writeNextConfig(options.getNextConfigOutputPath(), config, nextConfig);
    System.err.println(yml);
}
```

まずloadYamlConfigメソッドです

```java:Runner.java#174
private ConfigSource loadYamlConfig(String yamlPath)
{
    try {
        return injector.getInstance(ConfigLoader.class).fromYamlFile(new File(yamlPath));
    } catch (IOException ex) {
        throw new ConfigException(ex);
    }
}
```

ここでは先ほどのinjectorを使ってConfigLoader#fromYamlFileを呼び出してますね
内容は想像通りYamlからの設定ファイルロードなので割愛します

個人的に驚いたのがfromYamlFileというメソッド存在チェックをコンパイラが行っているというところです
getInstance自体はT型を返して来ているので、コンパイラチェック等は全く行えないのかと思っていたのですが。。。
推測可能になるとそこまで見てくれるんですね、素晴らしいです

次にcheckNextConfigOutputPathメソッドです
これはAppendモードでアウトプットファイルパスを開き、ファイルが書き込み可能状態かどうか調べているだけですね

```java:Runner.java#99
private void checkNextConfigOutputPath(String path)
{
    if (path != null) {
        try (FileOutputStream in = new FileOutputStream(path, true)) {
        // open with append mode and do nothing. just check availability of the path to not cause exceptiosn later
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
}
```

次は当処理の肝であるGuessExecutorです
ここは長くなるので次回に
ひとまずGuessExecutorにExecSessionとConfig情報を渡しているということだけ憶えていてください

最後にwriteNextConfigメソッドです


```java:Runner.java#110
private String writeNextConfig(String path, ConfigSource originalConfig, NextConfig nextConfigDiff)
{
    String yml = dumpConfigInYaml(originalConfig.merge(nextConfigDiff));
    if (path != null) {
        if (path.equals("-")) {
            System.out.print(yml);
        } else {
            try (Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(path), "UTF-8"))) 
            {
                writer.write(yml);
            } catch (IOException ex) {
                throw new RuntimeException(ex);
               }
        }
    }
    return yml;
}
```

ここも簡単で単純にアウトプットファイルにGuess済みの設定ファイルを書き出しているだけですね

ただ面白いのがpath.equals("-")という箇所です
隠しオプションとして-oに-を渡すと標準出力にGuess済みの設定内容が表示されるようです
まあ通常でも標準エラー出力には表示されるのであまり使い道はないでしょうが

というわけ今日はRunner.javaの中身を読みました

次回からは本丸GuessExecutorに移ります