Eclipse

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

More than 1 year has passed since last update.


メソッドの実装

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

プラグイン
使用するクラス

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

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

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;

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ファイルでない場合は、例外が発生して異常終了する。


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


変更前

public class Student {

private int id;
private String name;
private int age;
}


変更後

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;
}
}


実装部(インポート宣言)

import org.apache.commons.lang3.StringUtils;

import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.jdt.core.dom.*;


実装部(イベントハンドラ)

public Object execute(ExecutionEvent event) throws ExecutionException {

manipulateJavaSource(event, new CreatePropertyVisitor());
return null;
}


実装部(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を使うことで、Javaソースコードの抽象構文木を視覚的に捉えることができる。更に各ノードのクラスの名前がわかるため、Visitorを作るときには必須のプラグインといってよい。