Gradle および IntelliJ でモジュールを使ったときにリソースが取得できない問題

# 環境
**Java**

`openjdk 11 2018-09-25`

**Gradle**

`Gradle 4.10.2`

**IntelliJ**

```
IntelliJ IDEA 2018.2.4 (Community Edition)
Build #IC-182.4505.22, built on September 18, 2018
```

**OS**

`Windows 10`


# 発生する問題
## プロジェクト構成
```
|-build.gradle
`-src/main/
  |-java/
  | |-module-info.java
  | `-foo/
  |   `-Foo.java
  `-resources/
    `-resource.txt
```

## コード
```gradle:build.gradle
apply plugin: "application"

sourceCompatibility = 11
targetCompatibility = 11

compileJava.options.encoding = "UTF-8"
mainClassName = "foo/foo.Foo"

compileJava {
    doFirst {
        options.compilerArgs = [
            "--module-path", classpath.asPath
        ]
        classpath = files()
    }
}

run {
    doFirst {
        jvmArgs = [
            "--module-path", classpath.asPath,
            "--module", mainClassName
        ]
        classpath = files()
    }
}
```

```java:module-info.java
module foo {
    exports foo;
}
```

```java:Foo.java
package foo;

import java.net.URL;

public class Foo {

    public static void main(String[] args) throws Exception {
        URL resource = Foo.class.getResource("/resource.txt");
        System.out.println("resource=" + resource);
    }
}
```

## 実行結果
```
> gradle run
resource=null
```

- `/resource.txt` が取得できずに `null` になる
- IntelliJ に取り込んでも同じ結果になる
- ちなみに、 Eclipse (Photon) だと問題なく取得できる

# 原因（と思われるもの）
- Gradle と IntelliJ では、 `src/main/java` と `src/main/resources` のビルド結果が、それぞれ別のディレクトリに出力される

```text:gradleのビルド結果
|-build
: |-classes/java/main/
: | `-foo/
: |   `-Foo.class
: |
: |-resources/main/
: : `-resource.txt
```

```text:IntelliJのビルド結果
|-out/production/
: |-classes/
: | `-foo/
: |   `-Foo.class
: |
: `-resources/
:   `-resource.txt
```

- このとき、モジュールパスがどのように設定されるか確認する

```java:Foo.java
package foo;

import java.net.URL;
import java.util.stream.Stream;

public class Foo {

    public static void main(String[] args) throws Exception {
        System.out.println("[jdk.module.path]");
        Stream.of(System.getProperty("jdk.module.path")
                .split(";"))
                .map(p -> "  " + p)
                .forEach(System.out::println);

        URL resource = Foo.class.getResource("/resource.txt");
        System.out.println("resource=" + resource);
    }
}
```

```text:Gradleの実行結果
[jdk.module.path]
  ...\build\classes\java\main
  ...\build\resources\main
resource=null
```

```text:IntelliJの実行結果
[jdk.module.path]
  ...\out\production\classes
  ...\out\production\resources
resource=null
```

- それぞれがモジュールパスに設定されている
- つまり、 `resources` ディレクトリは `foo` モジュールとは別のモジュール扱いになっている気がする
    - `module-info.class` が無いディレクトリをモジュールパスに入れた場合は、無名モジュール扱いになる？？？
    - jar ファイルなら、ファイル名からモジュール名が解決されて自動モジュール扱いになるが、ディレクトリの場合はどうなるのかよくわかってない
- そして、  `Class.getResource(String)` の JavaDoc を確認すると、次のように説明されている

> このクラスが名前付きModuleにある場合、**このメソッドはモジュール内のリソースを検索しようとします**。
> （中略）
> リソースが**呼び出し元モジュールに対して開かれていないパッケージ内の非".class"リソースである場合、このメソッドはnullを返します**。

[getResource(String) | Class | Java 10](https://docs.oracle.com/javase/jp/10/docs/api/java/lang/Class.html#getResource(java.lang.String))

- つまり、 `Class.getResource(String)` は、そのクラスが属するモジュール内のリソースだけを検索しようとする
- もしリソースが他のモジュールだった場合、そのパッケージが開かれている(`opens` 指定されている？)必要がある
- もし `resources` が無名モジュールとして扱われているのであれば、名前付きモジュールから無名モジュールにはアクセスできない気がする
    - 少なくとも、クラスはアクセスできない
    - リソースもアクセスできないかは、正確なところはわかってない
- ちなみに、 Eclipse の場合は `src/main/java` も `src/main/resources` も、どちらも `bin/main` の下に出力されている

## 話を整理
- Gradle, IntelliJ の場合、 `src/main/resources` ディレクトリ以下のファイルは、 `src/main/java` 以下のコンパイル結果とは違うディレクトリに出力される
- `src/main/java`, `src/main/resources` の出力先は、別々のディレクトリとしてモジュールパスに指定されることになる
- `src/main/resources` は `src/main/java` とは異なるモジュール扱いになる(おそらく無名モジュール？)
- `Class.getResource(String)` は、基本的に同じモジュール内か、開かれているモジュール内のリソースにしかアクセスできないっぽい
- したがって、 `src/main/resources` 以下に配置したファイルを、リソースとして読み取ることができなくなる

あくまで予想。

# 解決方法
[Can't access resource with Java 10 – IDEs Support (IntelliJ Platform) | JetBrains](https://intellij-support.jetbrains.com/hc/en-us/community/posts/360000430279-Can-t-access-resource-with-Java-10)

同じようなことで困っている人がいるっぽい。

ここで最終的に説明されている方法だと、 `src/main/resources` の出力先を `src/main/java` の出力先と同じにすればいいとのこと。

```diff:build.gradle
apply plugin: "application"
+ apply plugin: 'idea'

sourceCompatibility = 11
targetCompatibility = 11

compileJava.options.encoding = "UTF-8"
mainClassName = "foo/foo.Foo"

+ sourceSets {
+     main {
+         output.resourcesDir = java.outputDir
+     }
+ }
+ 
+ idea.module.outputDir file("out/production/classes")

compileJava {
    doFirst {
        options.compilerArgs = [
            "--module-path", classpath.asPath
        ]
        classpath = files()
    }
}

run {
    doFirst {
        jvmArgs = [
            "--module-path", classpath.asPath,
            "--module", mainClassName
        ]
        classpath = files()
    }
}
```

- IntelliJ の出力先と、 Gradle の出力先をそれぞれ調整している

## 実行結果
```text:Gradleの実行結果
> gradle run
[jdk.module.path]
  ...\build\classes\java\main
resource=file:/.../build/classes/java/main/resource.txt
```

```text:IntelliJの実行結果
[jdk.module.path]
  ...\out\production\classes
resource=file:/.../out/production/classes/resource.txt
```

- `src/main/resources` の出力先を揃えることで、リソースにアクセスできるようになった