【Eclipse】エディタで開いているJavaソースコードをプログラマブルに書き換えるメソッド

## メソッドの実装

まずはじめに、プラグインプロジェクトを作成する。次に、必須プラグインに以下のプラグインを追加する。

| プラグイン | 使用するクラス |
|---|---|
| org.eclipse.core.resources | `IFile` |
| org.eclipse.jdt.core | `ICompilationUnit` `JavaCore` `AST` `ASTParser` `ASTVisitor` `CompilationUnit` |
| org.eclipse.jface.text | `BadLocationException` `IDocument` `MalformedTreeException` `TextEdit` |
| org.eclipse.ui.editors | `ITextEditor` |
| org.eclipse.ui.ide | `IFileEditorInput` |

最後に、以下のインポート宣言とメソッド定義を適切な場所に記述する。

```java
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.resources.IFile;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.handlers.HandlerUtil;
import org.eclipse.ui.texteditor.ITextEditor;
```

```java
public void manipulateJavaSource(ExecutionEvent event, ASTVisitor visitor) {

    ITextEditor editor = (ITextEditor)HandlerUtil.getActiveEditor(event);
    IFileEditorInput input = (IFileEditorInput)editor.getEditorInput();
    IDocument document = editor.getDocumentProvider().getDocument(input);
    IFile file = input.getFile();
    ICompilationUnit source = JavaCore.createCompilationUnitFrom(file);

    ASTParser parser = ASTParser.newParser(AST.JLS8);
    parser.setSource(source);
    CompilationUnit root = (CompilationUnit)parser.createAST(null);
    
    root.recordModifications();
    root.accept(visitor);
    
    TextEdit textEdit = root.rewrite(document, null);
    try {
        textEdit.apply(document);
    } catch (BadLocationException | MalformedTreeException e) {
        throw new RuntimeException(e);
    }
}
```

## メソッドの概要

* エディタがテキストエディタでない場合は、例外が発生して異常終了する。
* エディタの編集対象がJavaファイルでない場合は、例外が発生して異常終了する。

## 使用例：ゲッターとセッターの生成

```java:変更前
public class Student {
    private int id;
    private String name;
    private int age;
}
```

```java:変更後
public class Student {
    private int id;
    private String name;
    private int age;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}
```

```java:実装部（インポート宣言）
import org.apache.commons.lang3.StringUtils;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.jdt.core.dom.*;
```

```java:実装部（イベントハンドラ）
public Object execute(ExecutionEvent event) throws ExecutionException {
    manipulateJavaSource(event, new CreatePropertyVisitor());
    return null;
}
```

```java:実装部（Visitor定義）
private class CreatePropertyVisitor extends ASTVisitor {
    @Override
    public boolean visit(FieldDeclaration node) {
        TypeDeclaration parent = (TypeDeclaration)node.getParent();
        AST ast = parent.getAST();

        VariableDeclarationFragment var = (VariableDeclarationFragment)node.fragments().get(0);
        String name = var.getName().getIdentifier();
    
        // public Type getProperty() {
        //     return property;
        // }
        MethodDeclaration getter = ast.newMethodDeclaration();
        {
            // public
            getter.modifiers().add(ast.newModifier(Modifier.ModifierKeyword.PUBLIC_KEYWORD));

            // Type
            getter.setReturnType2((Type)ASTNode.copySubtree(ast, node.getType()));

            // getProperty
            getter.setName(ast.newSimpleName("get" + StringUtils.capitalize(name)));

            // {
            Block block = ast.newBlock();
            {
                // return property;
                ReturnStatement returnStatement = ast.newReturnStatement();
                {
                    returnStatement.setExpression(ast.newSimpleName(name));
                }
                block.statements().add(returnStatement);
            }
            getter.setBody(block);
            // }
        }

        // public void setProperty(Type property) {
        //     this.property = property;
        // }
        MethodDeclaration setter = ast.newMethodDeclaration();
        {
            // public
            setter.modifiers().add(ast.newModifier(Modifier.ModifierKeyword.PUBLIC_KEYWORD));
            
            // setProperty
            setter.setName(ast.newSimpleName("set" + StringUtils.capitalize(name)));
            
            // (Type property)
            SingleVariableDeclaration singleVariable =  ast.newSingleVariableDeclaration();
            singleVariable.setType((Type)ASTNode.copySubtree(ast, node.getType()));
            singleVariable.setName(ast.newSimpleName(name));
            setter.parameters().add(singleVariable);

            // {
            Block block = ast.newBlock();
            {
                // this.property = property;
                Assignment assignment = ast.newAssignment();
                {
                    // this.field
                    FieldAccess fieldAccess = ast.newFieldAccess();
                    fieldAccess.setExpression(ast.newThisExpression());
                    fieldAccess.setName(ast.newSimpleName(name));
                    assignment.setLeftHandSide(fieldAccess);
                    
                    // =
                    assignment.setOperator(Assignment.Operator.ASSIGN);
                    
                    // field
                    assignment.setRightHandSide(ast.newSimpleName(name));
                }
                block.statements().add(ast.newExpressionStatement(assignment));
            }
            setter.setBody(block);
            // }
        }

        parent.bodyDeclarations().add(getter);
        parent.bodyDeclarations().add(setter);
        
        return super.visit(node);
    }
}
```

* `singleVariable.setType(node.getType())`はエラーになる。これは、`node.getType()`によって得られる`Type`オブジェクトは`node`オブジェクトの子ノードであり、ノードは複数の親を持つことができないため。`singleVariable.setType()`によって設定できるのは、親を持たないノードのみ。`ASTNode.copySubtree()`によって複製されたノードは親を持たないため、`singleVariable.setType()`に渡してもエラーにならない。
* `visit()`のブロック内で`FieldDeclaration`オブジェクトを追加した場合は無限ループとなる。
* [AST View](http://www.eclipse.org/jdt/ui/astview/)を使うことで、Javaソースコードの抽象構文木を視覚的に捉えることができる。更に各ノードのクラスの名前がわかるため、Visitorを作るときには必須のプラグインといってよい。