플러그인은 JDT API를 사용하여 클래스나 인터페이스를 작성하고 기존 유형에 메소드를 추가하거나 유형에 대한 메소드를 변경할 수 있습니다.
Java 오브젝트를 변경하는 가장 간단한 방법은 Java 요소 API를 사용하는 것입니다. 보다 일반적인 기술이 Java 요소의 원시 소스 코드에 대한 작업을 위해 사용될 수 있습니다.
컴파일 단위를 프로그램에 따라 생성하는 가장 간단한 방법은 IPackageFragment.createCompilationUnit을 사용하는 것입니다. 컴파일 단위의 이름과 컨텐츠를 지정합니다. 컴파일 단위가 패키지에 작성되고 새 ICompilationUnit이 리턴됩니다.
보통 컴파일 단위는 패키지 디렉토리에 해당하는 적절한 폴더에 확장자가 ".java"인 파일 자원을 만들어서 작성됩니다. 일반 자원 API를 사용하면 Java 도구의 뒤로 이동하므로 일반 자원 변경 리스너가 알려지고 JDT 리스너가 Java 모델을 새 컴파일 단위로 갱신해야 Java 모델이 갱신됩니다.
Java 소스를 수정하는 가장 간단한 방법은 Java 요소 API를 사용하는 것입니다.
예를 들어, 컴파일 단위에서 유형을 조회할 수 있습니다. 일단 IType을 가지면, 유형으로 소스 코드 구성원을 추가하기 위해 createField, createInitializer, createMethod, 또는 createType과 같은 프로토콜을 사용할 수 있습니다. 구성원의 위치에 관한 정보와 소스 코드가 이러한 메소드에 제공됩니다.
ISourceManipulation 인터페이스는 Java 요소의 공통 소스 조작을 정의합니다. 여기에는 유형 구성원의 이름 바꾸기, 이동, 복사 또는 삭제 메소드가 포함됩니다.
코드는 컴파일 단위를 조작하여 수정되거나(숨겨진 IFile이 수정됨), 작업 사본으로 불리우는 컴파일 단위의 메모리내 사본을 수정할 수 있습니다.
작업 사본은 getWorkingCopy 메소드를 사용하여 컴파일 단위에서 얻을 수 있습니다. (작업 사본을 작성하기 위해 컴파일 단위가 Java 모델에 존재해야 하는 것은 아닙니다.) 이러한 작업 사본을 작성한 사용자가 누구이든 더 이상 필요하지 않을 때는 discardWorkingCopy 메소드를 사용하여 이를 버려야 하는 책임이 있습니다.
작업 사본은 메모리 내 버퍼를 수정합니다. getWorkingCopy() 메소드가 기본 버퍼를 작성하지만 클라이언트는 getWorkingCopy(WorkingCopyOwner, IProblemRequestor, IProgressMonitor) 메소드를 사용하여 자신의 버퍼 구현을 제공할 수 있습니다. 클라이언트는 이 버퍼의 텍스트를 직접 조작할 수 있습니다. 이러한 경우, 클라이언트는 reconcile(int, boolean, WorkingCopyOwner, IProgressMonitor) 메소드를 사용하여 때때로 버퍼와 작업 사본을 동기화해야 합니다.
마지막으로, 작업 사본은 commitWorkingCopy 메소드를 사용하여 디스크에 저장될 수 있습니다(원래 컴파일 단위를 대체함).
예를 들어, 다음 코드 스니펫은 사용자 작업 사본 소유자를 사용하여 컴파일 단위에서 작업 사본을 작성합니다. 스니펫은 버퍼를 수정하고 변경사항을 조정하며, 디스크에 대한 변경사항을 확약한 후 마지막에는 작업 사본을 버립니다.
// Get original compilation unit ICompilationUnit originalUnit = ...; // Get working copy owner WorkingCopyOwner owner = ...; // Create working copy ICompilationUnit workingCopy = originalUnit.getWorkingCopy(owner, null, null); // Modify buffer and reconcile IBuffer buffer = ((IOpenable)workingCopy).getBuffer(); buffer.append("class X {}"); workingCopy.reconcile(NO_AST, false, null, null); // Commit changes workingCopy.commitWorkingCopy(false, null); // Destroy working copy workingCopy.discardWorkingCopy();
작업 사본은 작업 사본 소유자를 사용하여 여러 클라이언트에 의해 공유될 수도 있습니다. 작업 사본은 나중에 findWorkingCopy 메소드를 사용하여 검색될 수 있습니다. 그러므로 공유 작업 사본은 원래 컴파일 단위와 작업 사본 소유자에 맞춰 조정됩니다.
다음은 클라이언트 1이 공유 작업 사본을 작성하고, 클라이언트 2가 이 작업 사본을 검색하며, 클라이언트 1이 작업 사본을 버린 후, 이 공유 작업 사본 검색을 시도하는 클라이언트 2가 작업 사본이 더 이상 존재하지 않음을 알리는 방법을 보여줍니다.
// Client 1 & 2: Get original compilation unit ICompilationUnit originalUnit = ...; // Client 1 & 2: Get working copy owner WorkingCopyOwner owner = ...; // Client 1: Create shared working copy ICompilationUnit workingCopyForClient1 = originalUnit.getWorkingCopy(owner, null, null); // Client 2: Retrieve shared working copy ICompilationUnit workingCopyForClient2 = originalUnit.findWorkingCopy(owner); // This is the same working copy assert workingCopyForClient1 == workingCopyForClient2; // Client 1: Discard shared working copy workingCopyForClient1.discardWorkingCopy(); // Client 2: Attempt to retrieve shared working copy and find out it's null workingCopyForClient2 = originalUnit.findWorkingCopy(owner); assert workingCopyForClient2 == null;
AST 팩토리 메소드를 사용하여 스크래치에서 CompilationUnit를 작성하는 것이 가능합니다. 이 메소드 이름은 new...로 시작됩니다. 다음은 HelloWorld 클래스 작성의 예입니다.
첫 번째 스니펫은 생성된 출력입니다.
package example;
import java.util.*;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello" + " world");
}
}
다음 스니펫은 출력을 생성한 해당 코드입니다.
AST ast = new AST();
CompilationUnit unit = ast.newCompilationUnit();
PackageDeclaration packageDeclaration = ast.newPackageDeclaration();
packageDeclaration.setName(ast.newSimpleName("example"));
unit.setPackage(packageDeclaration);
ImportDeclaration importDeclaration = ast.newImportDeclaration();
QualifiedName name =
ast.newQualifiedName(
ast.newSimpleName("java"),
ast.newSimpleName("util"));
importDeclaration.setName(name);
importDeclaration.setOnDemand(true);
unit.imports().add(importDeclaration);
TypeDeclaration type = ast.newTypeDeclaration();
type.setInterface(false);
type.setModifiers(Modifier.PUBLIC);
type.setName(ast.newSimpleName("HelloWorld"));
MethodDeclaration methodDeclaration = ast.newMethodDeclaration();
methodDeclaration.setConstructor(false);
methodDeclaration.setModifiers(Modifier.PUBLIC | Modifier.STATIC);
methodDeclaration.setName(ast.newSimpleName("main"));
methodDeclaration.setReturnType(ast.newPrimitiveType(PrimitiveType.VOID));
SingleVariableDeclaration variableDeclaration = ast.newSingleVariableDeclaration();
variableDeclaration.setModifiers(Modifier.NONE);
variableDeclaration.setType(ast.newArrayType(ast.newSimpleType(ast.newSimpleName("String"))));
variableDeclaration.setName(ast.newSimpleName("args"));
methodDeclaration.parameters().add(variableDeclaration);
org.eclipse.jdt.core.dom.Block block = ast.newBlock();
MethodInvocation methodInvocation = ast.newMethodInvocation();
name =
ast.newQualifiedName(
ast.newSimpleName("System"),
ast.newSimpleName("out"));
methodInvocation.setExpression(name);
methodInvocation.setName(ast.newSimpleName("println"));
InfixExpression infixExpression = ast.newInfixExpression();
infixExpression.setOperator(InfixExpression.Operator.PLUS);
StringLiteral literal = ast.newStringLiteral();
literal.setLiteralValue("Hello");
infixExpression.setLeftOperand(literal);
literal = ast.newStringLiteral();
literal.setLiteralValue(" world");
infixExpression.setRightOperand(literal);
methodInvocation.arguments().add(infixExpression);
ExpressionStatement expressionStatement = ast.newExpressionStatement(methodInvocation);
block.statements().add(expressionStatement);
methodDeclaration.setBody(block);
type.bodyDeclarations().add(methodDeclaration);
unit.types().add(type);
private int[] getOperatorPosition(Expression expression, char[] source) { if (expression instanceof InstanceofExpression) { IScanner scanner = ToolFactory.createScanner(false, false, false, false); scanner.setSource(source); int start = expression.getStartPosition(); int end = start + expression.getLength(); scanner.resetTo(start, end); int token; try { while ((token = scanner.getNextToken()) != ITerminalSymbols.TokenNameEOF) { switch(token) { case ITerminalSymbols.TokenNameinstanceof: return new int[] {scanner.getCurrentTokenStartPosition(), scanner.getCurrentTokenEndPosition()}; } } } catch (InvalidInputException e) { } } return null; }IScanner는 토큰으로 입력 소스를 나누는 데 사용됩니다. 각 토큰에는 ITerminalSymbols 인터페이스에 정의된 특정 값이 있습니다. 이것은 올바른 토큰의 반복 및 검색을 위해 매우 간단합니다. SuperMethodInvocation에서 super 키워드의 위치를 찾으려면 스캐너를 사용하십시오.
일부 소스 코드 수정은 Java 요소 API를 통해 제공되지 않습니다. 소스 코드를 편집(기존 요소의 소스 코드를 변경하는 것과 같은)하기 위한 보다 일반적인 방법은 컴파일 단위의 원시 소스 코드 및 DOM/AST의 API 다시 쓰기를 사용하여 수행됩니다.
DOM/AST 다시 쓰기를 수행하기 위해 두 가지 API 세트가 제공되는데, 이는 설명 다시 쓰기와 수정 다시 쓰기입니다.
설명 API는 AST를 수정하지 않지만
ASTRewrite API를
사용하여 수정사항의 설명을 생성합니다.
AST 재작성자는 노드에 대한 수정사항 설명을 수집하여 이들 설명을 원래 소스에 적용될 수 있는 텍스트 편집으로
변환합니다.
// creation of a Document
ICompilationUnit cu = ... ; // content is "public class X {\n}"
String source = cu.getBuffer().getContents();
Document document= new Document(source);
// creation of DOM/AST from a ICompilationUnit
ASTParser parser = ASTParser.newParser(AST.JLS2);
parser.setSource(cu);
CompilationUnit astRoot = (CompilationUnit) parser.createAST(null);
// creation of ASTRewrite
ASTRewrite rewrite = new ASTRewrite(astRoot.getAST());
// description of the change
SimpleName oldName = ((TypeDeclaration)astRoot.types().get(0)).getName();
SimpleName newName = astRoot.getAST().newSimpleName("Y");
rewrite.replace(oldName, newName, null);
// computation of the text edits
TextEdit edits = rewrite.rewriteAST(document, cu.getJavaProject().getOptions(true));
// computation of the new source code
edits.apply(document);
String newSource = document.get();
// update of the compilation unit
cu.getBuffer().setContents(newSource);
API를 수정하면 AST를 직접 수정할 수 있습니다.
// creation of a Document ICompilationUnit cu = ... ; // content is "public class X {\n}" String source = cu.getBuffer().getContents(); Document document= new Document(source); // creation of DOM/AST from a ICompilationUnit ASTParser parser = ASTParser.newParser(AST.JLS2); parser.setSource(cu); CompilationUnit astRoot = (CompilationUnit) parser.createAST(null); // start record of the modifications astRoot.recordModifications(); // modify the AST TypeDeclaration typeDeclaration = (TypeDeclaration)astRoot.types().get(0) SimpleName newName = astRoot.getAST().newSimpleName("Y"); typeDeclaration.setName(newName); // computation of the text edits TextEdit edits = astRoot.rewrite(document, cu.getJavaProject().getOptions(true)); // computation of the new source code edits.apply(document); String newSource = document.get(); // update of the compilation unit cu.getBuffer().setContents(newSource);
플러그인이 사후에 Java 요소에 대한 변경사항을 알아야 할 경우 Java IElementChangedListener를 JavaCore에 등록할 수 있습니다.
JavaCore.addElementChangedListener(new MyJavaElementChangeReporter());
addElementChangedListener(IElementChangedListener, int)를 사용 중에 관심있는 이벤트의 유형을 지정하여 좀 더 구체적일 수 있습니다.
예를 들어, 조정 조작 중 이벤트 청취에만 관심이 있는 경우,
JavaCore.addElementChangedListener(new MyJavaElementChangeReporter(), ElementChangedEvent.POST_RECONCILE);
JavaCore가 지원하는 이벤트 종류에는 두 가지가 있습니다.
Java 요소 변경 리스너는 자원 변경 리스너(자원 변경 추적에 설명)와 개념적으로 유사합니다. 다음 스니펫은 시스템 콘솔의 요소 델타를 인쇄하는 Java 요소 변경 보고서를 구현합니다.
public class MyJavaElementChangeReporter implements IElementChangedListener { public void elementChanged(ElementChangedEvent event) { IJavaElementDelta delta= event.getDelta(); if (delta != null) { System.out.println("delta received: "); System.out.print(delta); } } }
IJavaElementDelta에는 변경된 요소와 변경 유형을 설명하는 플래그가 있습니다. 대부분의 델타 트리는 Java 모델 레벨을 근거로 합니다. 클라이언트는 어떤 프로젝트가 변경되었는지 알아보기 위해 getAffectedChildren을 사용하여 이 델타를 탐색해야 합니다.
다음 예제 메소드는 델타를 통과하고 추가, 제거 및 수정된 요소를 인쇄합니다.
void traverseAndPrint(IJavaElementDelta delta) { switch (delta.getKind()) { case IJavaElementDelta.ADDED: System.out.println(delta.getElement() + " was added"); break; case IJavaElementDelta.REMOVED: System.out.println(delta.getElement() + " was removed"); break; case IJavaElementDelta.CHANGED: System.out.println(delta.getElement() + " was changed"); if ((delta.getFlags() & IJavaElementDelta.F_CHILDREN) != 0) { System.out.println("The change was in its children"); } if ((delta.getFlags() & IJavaElementDelta.F_CONTENT) != 0) { System.out.println("The change was in its content"); } /* Others flags can also be checked */ break; } IJavaElementDelta[] children = delta.getAffectedChildren(); for (int i = 0; i < children.length; i++) { traverseAndPrint(children[i]); } }
조작의 몇몇 종류는 Java 요소 변경 알림을 트리거할 수 있습니다. 다음은 일부 예입니다.
IResourceDelta에 대해 Java 요소 델타는 IWorkspaceRunnable를 사용하여 일괄처리될 수 있습니다. IWorkspaceRunnable에서 실행되는 일부 Java 모델로부터의 델타 결과는 병합되어 한번에 보고됩니다.
JavaCore 는 Java 요소 변경사항을 일괄처리 하기 위한 실행 메소드를 제공합니다.
예를 들어, 다음 코드 단편은 두 개의 Java 요소 변경 이벤트를 트리거합니다.
// Get package IPackageFragment pkg = ...; // Create 2 compilation units ICompilationUnit unitA = pkg.createCompilationUnit("A.java", "public class A {}", false, null); ICompilationUnit unitB = pkg.createCompilationUnit("B.java", "public class B {}", false, null);
반면에 다음 코드 단편은 1개의 Java 요소 변경 이벤트를 트리거합니다.
// Get package IPackageFragment pkg = ...; // Create 2 compilation units JavaCore.run( new IWorkspaceRunnable() { public void run(IProgressMonitor monitor) throws CoreException { ICompilationUnit unitA = pkg.createCompilationUnit("A.java", "public class A {}", false, null); ICompilationUnit unitB = pkg.createCompilationUnit("B.java", "public class B {}", false, null); } }, null);