仕事で困ったのでIntelliJのプラグインを作る

# はじめに
Javaを書いている時に、こんなクラスのインスタンスを生成する必要があるテストを書くことがあった。

```java

@Value
public class Sample {
    private final Integer a;
    private final String b;
    private final SomeClass c;
}
```

`SomeClass`の中にはまたいくつかのオブジェクトが入れ子になっており、このクラスのインスタンスを作るだけでなかなか疲弊してしまう。加えてそれぞれのフィールドに初期値を代入する必要があり、テストに関係ない（オブジェクトさえあればテストができる）どうでもいい初期値を型ごとに考えていた。チームメンバーからも同様の悩みが挙がっていたため、今回は`Intention Action`を利用したIntelliJのプラグインでどうにかしてみることにした。
正直なところ詳しいところは[公式リファレンス](https://pleiades.io/intellij/sdk/docs/basics.html)に全部載っているため、それを読めばいいんだと思う。あくまでこれはやってみた記事。

# Intention Actionsとは
IntelliJのEditor上で呼び出せる便利なアクション。MacのショートカットはOption（⌥）+Enter。WindowsだとAlt+Enterだと思う。
詳しい情報は[公式Help](https://www.jetbrains.com/help/idea/intention-actions.html)にある。

# プラグイン作成
## めざしたもの
クラスのフィールドに型ごとの初期値を入れたインスタンス生成の記述をクリップボードにコピーしてくれるようなプラグイン。
例えば

```java

@Value
public class Sample {
    private final Integer a;
    private final String b;
    private final SomeClass c;
}
```

のクラスで実行したら

```
new Sample(0, "", new SomeClass())
```

とテキトー初期化インスタンスをクリップボードにコピーする。

## つくったもの
[GitHub](https://github.com/Ikeponias/intellij-plugin)に上げた。色々修正するところはある。

### アクション
[実行したいアクション](#めざしたもの)を「〜Action」という名前でJavaで作成。

```java

public class CopyAction extends PsiElementBaseIntentionAction {

    private static final String NEW_LINE;
    private static final Map<String, String> initialValueLUT = new HashMap<>();

    static {
        initialValueLUT.put("boolean", "false");
        initialValueLUT.put("Boolean", "false");
        initialValueLUT.put("int", "0");
        initialValueLUT.put("byte", "(byte)0");
        initialValueLUT.put("Byte", "(byte)0");
        initialValueLUT.put("Integer", "0");
        initialValueLUT.put("String", "\"\"");
        initialValueLUT.put("BigDecimal", "new BigDecimal(\"0\")");
        initialValueLUT.put("Long", "0L");
        initialValueLUT.put("long", "0L");
        initialValueLUT.put("short", "(short)0");
        initialValueLUT.put("Short", "(short)0");
        initialValueLUT.put("Date", "new Date()");
        initialValueLUT.put("float", "0.0F");
        initialValueLUT.put("Float", "0.0F");
        initialValueLUT.put("double", "0.0D");
        initialValueLUT.put("Double", "0.0D");
        initialValueLUT.put("Character", "\'\'");
        initialValueLUT.put("char", "\'\'");
        initialValueLUT.put("LocalDateTime", "LocalDateTime.now()");
        initialValueLUT.put("LocalDate", "LocalDate.now()");
        initialValueLUT.put("List", "[]");

        NEW_LINE = System.getProperty("line.separator");
    }

    @Override
    public void invoke(@NotNull Project project, Editor editor,
                       @NotNull PsiElement element) throws IncorrectOperationException {
        if (element.getParent() instanceof PsiClass) {
            final PsiClass psiClass = ((PsiClass) element.getParent());
            final Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
            final String clipBoardText = makeText(psiClass.getName(), psiClass.getAllFields());
            final StringSelection selection = new StringSelection(clipBoardText);
            clipboard.setContents(selection, selection);
        }
    }

    @Override
    public boolean isAvailable(@NotNull Project project, Editor editor,
                               @NotNull PsiElement element) {
        return true;
    }

    @Nls
    @NotNull
    @Override
    public String getFamilyName() {
        return "";
    }

    @NotNull
    @Override
    public String getText() { return "Add Field to ClipBoard";}

    private String makeText(final String className, final PsiField[] psiFields) {
        String clipBoardText = "new " + className + "(" + NEW_LINE;
        clipBoardText += Arrays.stream(psiFields)
                                 .map(p -> "/* " + p.getName() + " */ " + Optional.ofNullable(initialValueLUT
                                                                       .get(p.getTypeElement().getFirstChild().getFirstChild().getText()))
                                                   .orElse("new " + p.getType()
                                                                            .toString()
                                                                            .split(":")[1] + "()"))
                                 .collect(
                                         Collectors.joining("," + NEW_LINE));
        clipBoardText += ")";

        return clipBoardText;
    }
}
```

### plugin.xml
今回行いたいアクションは`Intention Action`なので、`plugin.xml`に実行クラスを`IntentionAction`タグで囲って記載する。

```xml
  <extensions defaultExtensionNs="com.intellij">
    <intentionAction>
      <className>CopyAction</className>
    </intentionAction>
  </extensions>
```
拡張ポイントについては[プラグインの拡張ポイント](https://pleiades.io/intellij/sdk/docs/basics/plugin_structure/plugin_extensions_and_extension_points.html)から探す（IntentionActionsは[LangExtentionPoints.xml](https://upsource.jetbrains.com/idea-ce/file/idea-ce-4f9b5f89b2a19ce700b1373a465c16b28ed8ad52/platform/platform-resources/src/META-INF/LangExtensionPoints.xml)の43行目あたり）。


## つくったもの
とりあえず初期値をいれつつインスタンスを作成する記述をクリップボードにコピーするプラグインをつくれた。
![sample.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212284/cb8f6aad-61b1-586c-8e88-9be5560971c3.gif)