Gradle で RxJava を動かす

この冬こそ関数型プログラミングにデビューしようと思い立ち、RxJava なるものを動かしてみました。

## Gradle で Hello World

まず、gradle で Java プログラムを動かす方法を確認します。
適当なディレクトリ (gradle-hello とか) に Hello.java があるとします。

```Hello.java
public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}
```

ふつーはこうしますよね。

```console
$ javac Hello.java
$ java Hello
Hello, World!
$ 
```

簡単なプログラムならこれでもいいけど、ライブラリの指定とか面倒。
ってわけで、gradle の出番です。

### Gradle で Java プログラムを動かす

これを gradle で動かすには、まず build.gradle が必要です。

```build.gradle
apply plugin: 'java'
apply plugin: 'application'

mainClassName = 'Hello'
```

次に、src/main/java ディレクトリを作って、その中にソースを置きます。

```console
$ mkdir -p src/main/java
$ mv Hello.java src/main/java
$ tree
.
├── build.gradle
└── src
    └── main
        └── java
            └── Hello.java

3 directories, 2 files
$ 
```

あとは gradle におまかせ。

```console
$ gradle run
:compileJava
:processResources UP-TO-DATE
:classes
:run
Hello, World!

BUILD SUCCESSFUL

Total time: 5.164 secs

This build could be faster, please consider using the Gradle Daemon: https://docs.gradle.org/2.10/userguide/gradle_daemon.html
$ 
```

-q を付けるとおとなしくなります。

```console
$ gradle -q run
Hello, World!
$ 
```

コンパイルしたものは build ディレクトリに生成されてます。

```console
$ tree
.
├── build
│   ├── classes
│   │   └── main
│   │       └── Hello.class
│   ├── dependency-cache
│   └── tmp
│       └── compileJava
├── build.gradle
└── src
    └── main
        └── java
            └── Hello.java

9 directories, 3 files
$ 
```

ちなみに、build.gradle で指定した java というプラグインは Java のソースをコンパイルして jar などを作ってくれるものですが、実行するには application というプラグインも必要になります。
これだから Java ってやつは…。

### Gradle でコマンドライン引数

プログラムが動いたら、コマンドライン引数も扱いたいですよね。
ついでに、後でテストを書くことも考慮して、Hello.java を次のように書き換えます。

```Hello.java
public class Hello {
    public String message(String name) {
        return "Hello, " + name + "!";
    }

    public static void main(String[] args) {
        Hello hello = new Hello();
        if (args.length == 0) {
            System.out.println(hello.message("World"));
        } else {
            for (String name : args) {
                System.out.println(hello.message(name));
            }
        }
    }
}
```

引数を付けて実行してみましょう。

```console
$ gradle -q run Earth Moon Sun

FAILURE: Build failed with an exception.

* What went wrong:
Task 'Earth' not found in root project 'gradle-hello'.

* Try:
Run gradle tasks to get a list of available tasks. Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
$ 
```

残念ながら、コマンドライン引数は build.gradle で指定する必要があります。

```build.gradle
apply plugin: 'java'
apply plugin: 'application'

mainClassName = 'Hello'
run.args = ['Earth', 'Moon', 'Sun']
```

```console
$ gradle -q run
Hello, Earth!
Hello, Moon!
Hello, Sun!
$ 
```

または、-P オプションで指定する方法もあるけど、複数指定できないし、省略したら怒られるので、面倒。

```build.gradle
apply plugin: 'java'
apply plugin: 'application'

mainClassName = 'Hello'
run.args = [project.args]
```

```console
$ gradle -q run -Pargs="Earth Moon Sun"
Hello, Earth Moon Sun!
$ gradle -q run

FAILURE: Build failed with an exception.

* Where:
Build file '/Users/hoshi/gradle-hello/build.gradle' line: 5

* What went wrong:
A problem occurred evaluating root project 'gradle-hello'.
> Could not find property 'args' on root project 'gradle-hello'.

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
$ 
```

main なんか実行してないで、ちゃんとテストを書けってことなんでしょうね。
これだから Java ってやつは…。

### Gradle で JUnit

テストコードを書く場合は、まず build.gradle を次のように書き換えます。

```build.gradle
apply plugin: 'java'

repositories {
    mavenCentral()
}

dependencies {
    testCompile 'junit:junit:4.12'
}
```

次に、src/test/java ディレクトリを作って、テストクラスを置きます。

```console
$ mkdir -p src/test/java
$ vi src/test/java/HelloTest.java
...
$ tree src
src
├── main
│   └── java
│       └── Hello.java
└── test
    └── java
        └── HelloTest.java

4 directories, 2 files
$ 
```

```HelloTest.java
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class HelloTest {
    Hello hello;

    @Before
    public void setup() {
        hello = new Hello();
    }

    @Test
    public void testMessage() {
        assertEquals("Hello, World!", hello.message("World"));
    }
}
```

あとは gradle におまかせ。

```console
$ gradle test
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileTestJava
:processTestResources UP-TO-DATE
:testClasses
:test

BUILD SUCCESSFUL

Total time: 5.918 secs

This build could be faster, please consider using the Gradle Daemon: https://docs.gradle.org/2.10/userguide/gradle_daemon.html
$ 
```

build/reports/tests/index.html をブラウザで開くと、テスト結果のレポートが見れます。

## Gradle で RxJava

それでは RxJava を試してみましょう。
[ObserverパターンとHelloWorldからはじめるRxJava](http://qiita.com/chooblarin/items/1cb6a58f6e52b8792c52) を参考にしました。

```HelloRx1.java
import rx.Observable;

public class HelloRx1 {
    public static void main(String[] args) {
        String[] names = { "Ben", "George" };
        Observable.from(names).subscribe(name -> System.out.println("Hello, " + name + "!"));
    }
}
```

build.gradle もちょっと書き換えて、コマンドラインから実行するクラスを指定できるようにします。

```build.gradle
apply plugin: 'java'
apply plugin: 'application'

mainClassName = project.main

repositories {
    mavenCentral()
}

dependencies {
    compile 'io.reactivex:rxjava:1.1.0'
}
```

```console
$ gradle -q run -Pmain=HelloRx1
Hello, Ben!
Hello, George!
$ 
```

もひとつ。

```HelloRx2.java
import rx.Observable;
import rx.Observer;

public class HelloRx2 {
    public static void main(String[] args) {
        String[] names = { "Ben", "", "George" };

        Observable.from(names).subscribe(new Observer<String>() {
            @Override
            public void onNext(String name) {
                if (name.isEmpty()) {
                    throw new RuntimeException("Empty name");
                }
                System.out.println("Hello, " + name + "!");
            }

            @Override
            public void onCompleted() {
                System.out.println("Completed.");
            }

            @Override
            public void onError(Throwable e) {
                System.out.println("Error: " + e);
            }
        });
    }
}
```

```console
$ gradle -q run -Pmain=HelloRx2
Hello, Ben!
Error: java.lang.RuntimeException: Empty name
$ 
```

さらに高度な例。([Learning Reactive Programming with Java 8](https://www.packtpub.com/application-development/learning-reactive-programming-java-8) より拝借。)

```SumRx.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import rx.Observable;
import rx.Subscriber;
import rx.observables.ConnectableObservable;

public class SumRx {
    public static void main(String[] args) {
        System.out.println("start");
        ConnectableObservable<String> input = from(System.in).publish();
        Observable<Double> a = varStream("a", input);
        a.subscribe(v -> System.out.println("a = " + v));
        Observable<Double> b = varStream("b", input);
        b.subscribe(v -> System.out.println("b = " + v));
        Observable.combineLatest(a, b, (x, y) -> x + y).subscribe(sum -> System.out.println("sum = " + sum));
        input.connect();
    }

    public static Observable<String> from(InputStream stream) {
        return from(new BufferedReader(new InputStreamReader(stream)));
    }

    public static Observable<String> from(BufferedReader reader) {
        return Observable.create(new Observable.OnSubscribe<String>() {
            @Override
            public void call(Subscriber<? super String> subscriber) {
                if (subscriber.isUnsubscribed()) { return; }
                try {
                    while (!subscriber.isUnsubscribed()) {
                        String line = reader.readLine();
                        if (line == null || line.equals("exit")) { break; }
                        subscriber.onNext(line);
                    }
                } catch (IOException e) {
                    subscriber.onError(e);
                }
                if (!subscriber.isUnsubscribed()) {
                    subscriber.onCompleted();
                }
            }
        });
    }

    public static Observable<Double> varStream(String varName, Observable<String> input) {
        return input
                .map(str -> str.split(":"))
                .filter(array -> array.length == 2 && array[0].equals(varName))
                .map(array -> Double.parseDouble(array[1]));
    }
}
```

gradle で動かしたプログラムから標準入力を読み込むために、以下を追加。

```build.gradle
run {
    standardInput = System.in
}
```

```console
$ gradle -q run -Pmain=ch01.Sum
start
a:1
a = 1.0
a:2
a = 2.0
b:3
b = 3.0
sum = 5.0
b:4
b = 4.0
sum = 6.0
c:5
a:6
a = 6.0
sum = 10.0
b:7
b = 7.0
sum = 13.0
exit
$ 
```

あとは各自いろいろ試せばいいと思います。