2011년 9월 5일 월요일
JDT 개발 시 plugin 소스 코드 보기
이클립스에서 JDT 플러그인 개발을 할 때 참조할 만한 가장 훌륭한 소스는
org.eclipse.jdt.ui 플러그인에 있는 소스다.
구글에서 여러 번 검색을 해 보고 따라 해 봤지만 번번히 실패를 했다.
아래의 링크에 있는 대로 실행해 봤다.
http://musingsofaprogrammingaddict.blogspot.com/2010/08/retrieving-jdt-sources-with-eclipse-36.html
위의 포스트의 댓글에도 있는 것처럼 방법은 간단한데 동작하지 않는단다.
나도 그랬다. 이것저것 설정을 바꿔 봤는데 잘 동작하지 않았다.
방법은 간단하다.
plug Ins 뷰 (뷰가 보이지 않거든 Window - Show View - Plug-in Development 에서 선택) 에서
참조하고자 하는 소스코드가 있는 플러그인(ex org.eclipse.jdt.ui) 을 선택한 후 오른쪽 마우스 버튼을 클릭하여
Import As - Project from a repository 메뉴를 실행시킨다.
워크스페이스에 org.eclipse.jdt.ui라는 프로젝트가 생성이 되었지만 여전히 자바 소스 코드를 내려 받지 않았다.
Import AS의 다른 메뉴를 실행하다 다시 위의 메뉴를 실행 보니 CVS 아이디와 비밀번호를 묻는 창이 띄워졌다.
아이디 입력란에 anonymous라고 입력하고 비밀번호는 비워둔 채로 OK 버튼을 클릭하니까
나의 로컬 워크스페이스에 org.eclipse.jdt.ui 라는 프로젝트가 생성되고 관련 자바 소스 코드가 내려 받아 졌다.
뒷걸음치다 쥐 잡은 꼴이지만 암튼 도움이 되시길..
참고로
Window - Preferences - Plug-in Developement 의 Source (Show Source plugins) 체크 박스도 설정해 보기도 하고 안 하기도 하고 그랬다. ^^
2010년 4월 9일 금요일
클래스 소스에 Delegate메서드 생성하기-JDT
이클립스에 Delegate 메서드 생성이라는 재미 있는 기능이 있다.
디자인 패턴을 공부해본 사람이라면 Delegate 패턴이라는 말이 익숙할 것이다.
간단히 설명하면 다음과 같다.
자바 클래스는 다중 상속을 지원하지 않는다. 이것과 관련이 깊다.
예를 들어 보자.
SomeClass에 goodMethod가 있다. 이것을 AnotherClass에서도 사용하고자 하는 경우가 있다.
가장 간단한 해결책은 상속이다.
이렇게 사용하면 간단하긴 하지만 디자인 패턴에서는 상속을 그렇게 권장하지는 않는다.
하나밖에 없는 자리(상속)를 이미 사용해 버리면 향후에 후회할 일이 생길 가능성이 많기 때문이다.
무엇 보다도 AnotherClass와 SomeClass가 의미상으로 서로 상속관계를 맺는게 어색하다.
SomeClass의 메소드를 편하게 사용하는 것이 이유라면 말이다.
그래서 디자인패턴에서는 이러한 문제의 해결책으로 Delegate 패턴을 제시한다.
언뜻 봐도 프로그래머가 할 일이 몇배는 많아 지는 것 같은 느낌이 든다. goodMethod 하나만 이렇게 하지는 않을 것이다. 아마도 SomeClass의 대부분의 메서드를 위와 같이 delegete 메서드를 만드는 것이 선뜻 내키지는 않을 것이다.
그래서 Eclipse는 Source -> Generate Delegate Methods 라는 훌륭한 메뉴를 만들어 제공하고 있다.
Delegate 메서드를 생성하고자 하는 필드를 선택하기만 하면 위와 같이 자동으로 소스코드를 생성해 준다.
Eclipse Plugin 개발자는 이런 JDT의 기본 액션에 감사를 해야 한다. ^^
나의 요구 사항은 다음과 같다.
Proxy based API로 구현된 RESTful 클라이언트를 생성하는 Wizard를 만들어라. 단 baseAddress와 proxy 인터페이스 정보는 Wizard에서 입력받는다.
즉 proxy에 정의된 메서드를 모두 delegate하는 소스를 생성해야 했다.
아래의 소스는 생성자와 프로퍼티를 추가하고 프로퍼티에 대한 delegate 메소드를 추가하는 예제 소스이다. 눈여겨 볼 부분은 StubUtility2.getDelegatableMethods()와 전체 DelegateEntry 중에서 proxy와 관련된 엔트리만 필터링하는 부분이다.
디자인 패턴을 공부해본 사람이라면 Delegate 패턴이라는 말이 익숙할 것이다.
간단히 설명하면 다음과 같다.
자바 클래스는 다중 상속을 지원하지 않는다. 이것과 관련이 깊다.
예를 들어 보자.
SomeClass에 goodMethod가 있다. 이것을 AnotherClass에서도 사용하고자 하는 경우가 있다.
가장 간단한 해결책은 상속이다.
public class AnotherClass extends SomeClass {
}
이렇게 사용하면 간단하긴 하지만 디자인 패턴에서는 상속을 그렇게 권장하지는 않는다.
하나밖에 없는 자리(상속)를 이미 사용해 버리면 향후에 후회할 일이 생길 가능성이 많기 때문이다.
무엇 보다도 AnotherClass와 SomeClass가 의미상으로 서로 상속관계를 맺는게 어색하다.
SomeClass의 메소드를 편하게 사용하는 것이 이유라면 말이다.
그래서 디자인패턴에서는 이러한 문제의 해결책으로 Delegate 패턴을 제시한다.
public class AnotherClass {
private SomeClass someClass = new SomeClass();
public void goodMethod() {
someClass.goodMethod();
}
}
언뜻 봐도 프로그래머가 할 일이 몇배는 많아 지는 것 같은 느낌이 든다. goodMethod 하나만 이렇게 하지는 않을 것이다. 아마도 SomeClass의 대부분의 메서드를 위와 같이 delegete 메서드를 만드는 것이 선뜻 내키지는 않을 것이다.
그래서 Eclipse는 Source -> Generate Delegate Methods 라는 훌륭한 메뉴를 만들어 제공하고 있다.
Delegate 메서드를 생성하고자 하는 필드를 선택하기만 하면 위와 같이 자동으로 소스코드를 생성해 준다.
Eclipse Plugin 개발자는 이런 JDT의 기본 액션에 감사를 해야 한다. ^^
나의 요구 사항은 다음과 같다.
Proxy based API로 구현된 RESTful 클라이언트를 생성하는 Wizard를 만들어라. 단 baseAddress와 proxy 인터페이스 정보는 Wizard에서 입력받는다.
private com.archnal.amf.sample.amfws.rs.SampleUserRs proxy = null;
public RestfulClientImpl() {
this.proxy = org.apache.cxf.jaxrs.client.JAXRSClientFactory.create("http://localhost:8080/amfws/restful", com.archnal.amf.sample.amfws.rs.SampleUserRs.class);
}
즉 proxy에 정의된 메서드를 모두 delegate하는 소스를 생성해야 했다.
아래의 소스는 생성자와 프로퍼티를 추가하고 프로퍼티에 대한 delegate 메소드를 추가하는 예제 소스이다. 눈여겨 볼 부분은 StubUtility2.getDelegatableMethods()와 전체 DelegateEntry 중에서 proxy와 관련된 엔트리만 필터링하는 부분이다.
private void modifyBeanClass(IProgressMonitor monitor) {
try {
String ln = IAmfUIConstants.LINE_SEP;
IType beanType = ProjectHelper.getJavaType(javaProject, beanClassName);
String fieldContents = "private " + proxyInterface + " proxy = null;" + ln;
IField field = beanType.createField(fieldContents, null, true, monitor);
StringBuffer sb = new StringBuffer();
sb.append("public ").append(beanType.getElementName()).append("() {").append(ln);
sb.append("\tthis.proxy = ").append("org.apache.cxf.jaxrs.client.JAXRSClientFactory.create(\"");
sb.append(baseAddress).append("\", ").append(proxyInterface).append(".class);").append(ln);
sb.append("}").append(ln);
IMethod constructor = beanType.createMethod(sb.toString(), null, true, monitor);
ProjectHelper.findJavaElement(javaProject, beanClassName).save(monitor, true);
CompilationUnit astRoot = ProjectHelper.parse(beanType);
VariableDeclarationFragment fragment =
ASTNodeSearchUtil.getFieldDeclarationFragmentNode(field, astRoot);
CodeGenerationSettings settings =
JavaPreferencesSettings.getCodeGenerationSettings(javaProject);
settings.createComments = true;
final ITypeBinding binding = ASTNodes.getTypeBinding(astRoot, beanType);
if(binding != null) {
DelegateEntry[] entries = StubUtility2.getDelegatableMethods(binding);
Listresult = new ArrayList ();
if(fragment != null) {
IVariableBinding varBinding = fragment.resolveBinding();
for(int i = 0; i < entries.length; i++) {
if(varBinding == entries[i].field) {
result.add(entries[i]);
}
}
}
DelegateEntry[] methodToDelegate = result.toArray(new DelegateEntry[result.size()]);
AddDelegateMethodsOperation operation =
new AddDelegateMethodsOperation(astRoot, methodToDelegate, null, settings, true, true);
operation.run(monitor);
}
} catch(Exception ex) {
AmfPlugin.log(ex);
}
}
클래스 생성 시 인터페이스 메서드 implements 하기 - JDT
이클립스에서 클래스를 생성할 때 클랫스 생성 다이얼로그에서 인터페이스를 지정하면
생성된 클래스 소스 안에 인터페이스에 정의된 메서드들이 override되어 있다.
메서드 내의 로직은 없지만 컴파일 에러가 발생하지 않을 정도의 소스 코드가 생성된다.
이러한 작업은 클래스가 생성 된 이후에도 가능하다.
특정 인터페이스를 implements 한 후에 프로젝트 탐색뷰에서 해당 클래스를 선택한 후 팝업메뉴를 띄우거나 현재 작업 중인 에디터에서 팝업메뉴를 띄운 후 Source -> Override/Implement Methods 를 선택하면 override 하거나 implement 할 메서드 목록을 선택할 수 있는 창이 열린다.
메서드를 선택한 후 OK버튼을 클릭하면 메서드가 override/implements 된다.
요구사항 : 특정 인터페이스를 상속하는 클래스를 생성한다. 생성된 클래스는 인터페이스 메서드를 implements 한다.
JDT UI에서 처리되는 이러한 작업을 프로그래밍적으로 구현해야 한다면 어떻게 해야할까?
먼저 JDT에서는 어떻게 구현되었는지 살펴보았다.
org.eclipse.jdt.ui.actions.OverrideMethodsAction 에서 org.eclipse.jdt.internal.ui.dialogs.OverrideMethodDialog 으로부터 선택된 결과(org.eclipse.jdt.core.dom.IMethodBinding 목록)를 가지고 org.eclipse.jdt.internal.corext.codemanipulation.AddUnimplementedMethodsOperation 을 호출하는 구조로 되어 있다.
나의 요구사항에서는 OverrideMethodDialog가 필요없다. implements해야 할 메서드들이 이미 결정되어 있기 때문이다. 그래서 implements할 메서드 목록을 IMethodBinding으로 뽑아 내기만 하면 작업이 거의 완성될 것으로 판단했다. 어차피 나머지 작업은 AddUnimplementedMethodsOperation에서 처리될 것이기 때문이다.
스텁 코드에 대한 처리를 할 일이 있다면 org.eclipse.jdt.internal.corext.codemanipulation.StubUtility2 클래스에 대해서 알아두는 것이 유익하다.
아래의 소스 코드는 StubUtility2에서 IMethodBinding을 뽑아내는 로직이 들어있다.
살펴보기 바란다.
AddUnimplementedMethodsOperation의 생성자의 파라미터는 마지막 3개의 boolean 파라미터는 각각
imports: if the import edits should be applied
apply: if the resulting edit should be applied
save: if the changed compilation unit should be saved
참고로, 에디터가 열린 상태로 소스 코드를 생성하는 것이 아니기 때문에 save는 반드시 true로 지정되어한다.
생성된 클래스 소스 안에 인터페이스에 정의된 메서드들이 override되어 있다.
메서드 내의 로직은 없지만 컴파일 에러가 발생하지 않을 정도의 소스 코드가 생성된다.
이러한 작업은 클래스가 생성 된 이후에도 가능하다.
특정 인터페이스를 implements 한 후에 프로젝트 탐색뷰에서 해당 클래스를 선택한 후 팝업메뉴를 띄우거나 현재 작업 중인 에디터에서 팝업메뉴를 띄운 후 Source -> Override/Implement Methods 를 선택하면 override 하거나 implement 할 메서드 목록을 선택할 수 있는 창이 열린다.
메서드를 선택한 후 OK버튼을 클릭하면 메서드가 override/implements 된다.
요구사항 : 특정 인터페이스를 상속하는 클래스를 생성한다. 생성된 클래스는 인터페이스 메서드를 implements 한다.
JDT UI에서 처리되는 이러한 작업을 프로그래밍적으로 구현해야 한다면 어떻게 해야할까?
먼저 JDT에서는 어떻게 구현되었는지 살펴보았다.
org.eclipse.jdt.ui.actions.OverrideMethodsAction 에서 org.eclipse.jdt.internal.ui.dialogs.OverrideMethodDialog 으로부터 선택된 결과(org.eclipse.jdt.core.dom.IMethodBinding 목록)를 가지고 org.eclipse.jdt.internal.corext.codemanipulation.AddUnimplementedMethodsOperation 을 호출하는 구조로 되어 있다.
나의 요구사항에서는 OverrideMethodDialog가 필요없다. implements해야 할 메서드들이 이미 결정되어 있기 때문이다. 그래서 implements할 메서드 목록을 IMethodBinding으로 뽑아 내기만 하면 작업이 거의 완성될 것으로 판단했다. 어차피 나머지 작업은 AddUnimplementedMethodsOperation에서 처리될 것이기 때문이다.
스텁 코드에 대한 처리를 할 일이 있다면 org.eclipse.jdt.internal.corext.codemanipulation.StubUtility2 클래스에 대해서 알아두는 것이 유익하다.
아래의 소스 코드는 StubUtility2에서 IMethodBinding을 뽑아내는 로직이 들어있다.
살펴보기 바란다.
IType javaType = ProjectHelper.findType(javaProject, serviceClass);
CompilationUnit unit = ProjectHelper.parse(javaType);
final ITypeBinding binding = ASTNodes.getTypeBinding(unit, javaType);
if(binding != null) {
final IPackageBinding pack = binding.getPackage();
// 클래스에서 override하거나 implements 해야 할 모든 메서드 목록에 대한 정보를 리턴한다.
final IMethodBinding[] methods =
StubUtility2.getOverridableMethods(unit.getAST(), binding, false);
Listlist = new ArrayList ();
for(IMethodBinding methodBinding: methods) {
// 상위 인터페이스에 정의된 메서드 목록만 추려낸다.
if(proxyInterface.equals(methodBinding.getDeclaringClass().getQualifiedName())) {
list.add(methodBinding);
}
}
IMethodBinding[] methodToOverride = list.toArray(new IMethodBinding[list.size()]);
try {
final ITypeBinding typeBinding= ASTNodes.getTypeBinding(unit, javaType);
// 시작위치는 지정하지 않는다. 가장 마지막에 추가될 것으로 판단된다.
int insertPos = -1;
// 오퍼레이션을 실행한다.
AddUnimplementedMethodsOperation operation = (AddUnimplementedMethodsOperation) createRunnable(unit, typeBinding, methodToOverride, insertPos, true);
operation.run(monitor);
} finally {
}
}
................
public static IWorkspaceRunnable createRunnable(CompilationUnit astRoot, ITypeBinding type, IMethodBinding[] methodToOverride, int insertPos, boolean createComments) {
AddUnimplementedMethodsOperation operation= new AddUnimplementedMethodsOperation(astRoot, type, methodToOverride, insertPos, true, true, true);
operation.setCreateComments(createComments);
return operation;
}
AddUnimplementedMethodsOperation의 생성자의 파라미터는 마지막 3개의 boolean 파라미터는 각각
imports: if the import edits should be applied
apply: if the resulting edit should be applied
save: if the changed compilation unit should be saved
참고로, 에디터가 열린 상태로 소스 코드를 생성하는 것이 아니기 때문에 save는 반드시 true로 지정되어한다.
2010년 4월 5일 월요일
AST로 배열값을 설정하는 annotation 추가하기
javax.ws.rs.Produces annotation은 문자열 배열을 값으로 가진다.
@Produces({"application/xml", "application/json"})
배열값을 가지는 annotation들은 기본적으로 1개만 지정되더라고 또는 전혀 지정되지 않더라도 문제가 되지 않는다.
@Produces("application/xml") 처럼 문자열 1개만 설정하도록 자바 소스를 생성하는 예제는 http://credemol.blogspot.com/2010/03/jdt-ast-abstract-syntax-tree.html에서 예제 소스를 작성해 두었다.
이번 글은 배열 값을 처리하는 것을 중심으로 설명하도록 한다.
@Produces(new String[] { "application/xml", "application/json"})
위와 같이 annotation을 설정했다면 어떻게 될까?
예상하는 바와 같이 "The value for annotation attribute Produces.value must be an array initializer" 라는 메시지가 출력되는 컴파일에러가 발생한다.
AST 용어로 접근하자면 new String[]은 ArrayCreation 이고, {"application/xml", "application/json"} 은 ArrayInitializer가 된다.
위의 에러메시지에서도 확인할 수 있듯이 annotation의 값은 array initializer가 되어야 한다.
@Produces와 같이 name=value 포맷으로 속성을 지정하는 것이 아니라 단순히 값만 지정하는 annotation은 SingleMemberAnnotation이다.
아래의 소스코드를 살펴보면 쉽게 이해할 수 있을 것이다.
예제 소스
@Produces({"application/xml", "application/json"})
배열값을 가지는 annotation들은 기본적으로 1개만 지정되더라고 또는 전혀 지정되지 않더라도 문제가 되지 않는다.
@Produces("application/xml") 처럼 문자열 1개만 설정하도록 자바 소스를 생성하는 예제는 http://credemol.blogspot.com/2010/03/jdt-ast-abstract-syntax-tree.html에서 예제 소스를 작성해 두었다.
이번 글은 배열 값을 처리하는 것을 중심으로 설명하도록 한다.
@Produces(new String[] { "application/xml", "application/json"})
위와 같이 annotation을 설정했다면 어떻게 될까?
예상하는 바와 같이 "The value for annotation attribute Produces.value must be an array initializer" 라는 메시지가 출력되는 컴파일에러가 발생한다.
AST 용어로 접근하자면 new String[]은 ArrayCreation 이고, {"application/xml", "application/json"} 은 ArrayInitializer가 된다.
위의 에러메시지에서도 확인할 수 있듯이 annotation의 값은 array initializer가 되어야 한다.
@Produces와 같이 name=value 포맷으로 속성을 지정하는 것이 아니라 단순히 값만 지정하는 annotation은 SingleMemberAnnotation이다.
아래의 소스코드를 살펴보면 쉽게 이해할 수 있을 것이다.
예제 소스
public static void addSingleMemberAnnotation(AST ast, TypeDeclaration type, String annotationType, String[] literalValues) {
SingleMemberAnnotation anno = ast.newSingleMemberAnnotation();
anno.setTypeName(createTypeName(ast, annotationType));
ArrayInitializer arrayInitializer = ast.newArrayInitializer();
for(String value : literalValues) {
StringLiteral literal = ast.newStringLiteral();
literal.setLiteralValue(value.trim());
arrayInitializer.expressions().add(literal);
}
anno.setValue(arrayInitializer);
List list = (List) type.getStructuralProperty(TypeDeclaration.MODIFIERS2_PROPERTY);
list.add(anno);
}
2010년 3월 6일 토요일
JDT AST (Abstract Syntax Tree)
AST는 JDT에서만 사용하는 용어가 아니라 일반적인 용어다.
일반적인 컴파일러나 자바 바이트 코드 관련 도구들을 찾아 보더라도 AST라는 용어를 자주 접하게 된다.
프로그램 언어가 트리구조의 문법을 가지고 있기 때문에 소스 코드를 변경해야 하거나 클래스를 동적으로 변경하고자 한다면 AST에 대한 이해를 하고 있어야 한다.
자바 바이트 코드를 동적으로 생성하는 기능을 제공하는 라이브러리로는 asm, cglib, javassist 등이 있다.
각각의 장단점은 있겠지만, javassist가 개발하는데는 쉽고 편하다.
JDT는 Runtime에 클래스를 변경하는 이것들과는 좀 다르다. JDT는 우리가 이해하는 것처럼 자바 개발 환경인 Eclipse의 자바 개발 툴이다. 물론 컴파일러로 클래스도 생성은 가능 하지만 어디까지나 개발 도중에 이용할 목적이지 Runtime에 이용할 목적은 아니다.
JDT의 AST로는 자바소스코드 편집에 관한 모든 일을 할 수 있다고 생각하면 된다.
자바에 익숙한 개발자라면 Velocity 같은 템플릿 엔진이나 심지어는 StringBuffer로도 자바소스를 만들어 낼 것이다.
하지만 이미 만들어진 자바 소스 내에 javaElement를 추가, 삭제하거나 변환 할 때, Javadoc을 추가할 때 자바 소스코드를 가지고는 처리할 수가 없다.
이클립스 플러그인으로 자바 소스코드를 처리해야하는 경우가 있다면 반드시 AST도 함께 보면 좋을 것이다.
보통은 참조 리소스를 가장 마지막에 소개하는데, AST관련하여 비교적 잘 정리가 되어 있어 http://www.ibm.com/developerworks/opensource/library/os-ast/?ca=dgr-lnxw961ASTParser 를 방문하기를 권고한다. 좀 유통기한이 지난 감이 없지 않지만, 나에겐 많은 도움이 된 문서다.
요구사항은 다음과 같다.
다이얼로그에서 스프링 빈으로 등록할 빈 클래스와 그 빈클래스에서 사용하는 프러퍼티를 입력받아 빈 클래스를 생성한다. 스프링에서의 빈은 의존성이 낮은 방식으로 주입되어야 하기 때문에 빈 클래스에 대한 인터페이스도 생성한다. 빈과 프로퍼티에 관련된 커멘트도 함께 생성한다.
자바 프로젝트의 src 폴더에 패키지명에 맞는 (IFolder)폴더를 생성한 후 자바(인터페이스, 클래스)파일을 저장한다.
패키지명에 따라 계층적으로 폴더를 계속 생성해야 하므로 ProjectHelper.createFolder() 유틸리티 클래스를 만들었다. 다른 항목은 별도로 언급할 만큼 어려운 코드가 아니다. 파일을 생성할 때 InputStream 으로 전달해주면 되기 때문에 굳이 AST를 사용하지 않고 문자열처리로도 가능하다. 하지만 AST 관련 글이라는 점을 계속 기억하고 있길 바란다.
아래코드는 인터페이스 파일을 생성하는 소스 코드지만 buildInterface를 제외하면 클래스를 만드는 소스코드에서도 동일한다.
인터페이스 생성은 아주 간단한다. 패키지명과 인터페이스 명만 있으면 생성이 가능해서 그냥 문자열로 처리해도 무리가 없을 정도이다.
인터페이스명으로 aa.bb.cc.dd.DDD 라고 입력 받았다면 아래와 같이 생성하면 된다.
실제 위와 같은 문자열을 만들어 내는 소스 코드는 다음과 같다.
클래스 생성은 조금 복잡하다. 인터페이스를 implements도 해주어야 하고, 필드도 추가해야하고, getter/setter 메서드도 만들어야 한다.
시작부분은 인터페이스와 비슷하다.
코드 중간에 있는 BeanPropertyModel 는 클래스는 필드 설정에 관련된 도메인 클래스로 필드명과 fullyQualifiedName으로 저장되어 있는 필드 타입, 그리고 필드 커멘트 등이 포함되어 있다.
소스 코드 중간에 JdtHelper라는 유틸리티 클래스에서 Javadoc을 생성하거나 getter/setter 등을 생성한다.
Assignment라든지 메서드에 파라미터 전달하는 방법, this에 접근하는 방법등이 아래 소스 코드에 포함되어 있다.
Resources
http://devdaily.com/java/jwarehouse/eclipse/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverterTest.java.shtml
http://www.ibm.com/developerworks/opensource/library/os-ast/?ca=dgr-lnxw961ASTParser
http://java.chinaitlab.com/Eclipse/38001.html
일반적인 컴파일러나 자바 바이트 코드 관련 도구들을 찾아 보더라도 AST라는 용어를 자주 접하게 된다.
프로그램 언어가 트리구조의 문법을 가지고 있기 때문에 소스 코드를 변경해야 하거나 클래스를 동적으로 변경하고자 한다면 AST에 대한 이해를 하고 있어야 한다.
자바 바이트 코드를 동적으로 생성하는 기능을 제공하는 라이브러리로는 asm, cglib, javassist 등이 있다.
각각의 장단점은 있겠지만, javassist가 개발하는데는 쉽고 편하다.
JDT는 Runtime에 클래스를 변경하는 이것들과는 좀 다르다. JDT는 우리가 이해하는 것처럼 자바 개발 환경인 Eclipse의 자바 개발 툴이다. 물론 컴파일러로 클래스도 생성은 가능 하지만 어디까지나 개발 도중에 이용할 목적이지 Runtime에 이용할 목적은 아니다.
JDT의 AST로는 자바소스코드 편집에 관한 모든 일을 할 수 있다고 생각하면 된다.
자바에 익숙한 개발자라면 Velocity 같은 템플릿 엔진이나 심지어는 StringBuffer로도 자바소스를 만들어 낼 것이다.
하지만 이미 만들어진 자바 소스 내에 javaElement를 추가, 삭제하거나 변환 할 때, Javadoc을 추가할 때 자바 소스코드를 가지고는 처리할 수가 없다.
이클립스 플러그인으로 자바 소스코드를 처리해야하는 경우가 있다면 반드시 AST도 함께 보면 좋을 것이다.
보통은 참조 리소스를 가장 마지막에 소개하는데, AST관련하여 비교적 잘 정리가 되어 있어 http://www.ibm.com/developerworks/opensource/library/os-ast/?ca=dgr-lnxw961ASTParser 를 방문하기를 권고한다. 좀 유통기한이 지난 감이 없지 않지만, 나에겐 많은 도움이 된 문서다.
요구사항은 다음과 같다.
다이얼로그에서 스프링 빈으로 등록할 빈 클래스와 그 빈클래스에서 사용하는 프러퍼티를 입력받아 빈 클래스를 생성한다. 스프링에서의 빈은 의존성이 낮은 방식으로 주입되어야 하기 때문에 빈 클래스에 대한 인터페이스도 생성한다. 빈과 프로퍼티에 관련된 커멘트도 함께 생성한다.
1. 자바 엘리먼트 저장 매카니즘
자바 프로젝트의 src 폴더에 패키지명에 맞는 (IFolder)폴더를 생성한 후 자바(인터페이스, 클래스)파일을 저장한다.
패키지명에 따라 계층적으로 폴더를 계속 생성해야 하므로 ProjectHelper.createFolder() 유틸리티 클래스를 만들었다. 다른 항목은 별도로 언급할 만큼 어려운 코드가 아니다. 파일을 생성할 때 InputStream 으로 전달해주면 되기 때문에 굳이 AST를 사용하지 않고 문자열처리로도 가능하다. 하지만 AST 관련 글이라는 점을 계속 기억하고 있길 바란다.
아래코드는 인터페이스 파일을 생성하는 소스 코드지만 buildInterface를 제외하면 클래스를 만드는 소스코드에서도 동일한다.
private void createBeanInterface() throws CoreException{
try {
String interfaceName = beanInterfaceName;
int index = interfaceName.lastIndexOf('.');
String packageName = (index < 0) ? null : interfaceName.substring(0, index);
String simpleName = interfaceName.substring(index + 1);
IFolder packageFolder = null;
if(packageName != null) {
Path packagePath = new Path(packageName.replace('.', '/'));
IFolder srcFolder = ProjectHelper.getSourceFolder(javaProject.getProject());
packageFolder = ProjectHelper.createFolder(srcFolder, packagePath);
} else {
packageFolder = ProjectHelper.getSourceFolder(javaProject.getProject());
}
IFile javaFile = packageFolder.getFile(simpleName + ".java");
if(javaFile.exists() == false) {
javaFile.create(new ByteArrayInputStream(buildInterface(interfaceName).getBytes()), true, null);
}
} catch(Exception ex) {
ex.printStackTrace();
throw new CoreException(DialogHelper.createErrorStatus("Interface Creation ERROR", ex));
}
}
2. 인터페이스 생성
인터페이스 생성은 아주 간단한다. 패키지명과 인터페이스 명만 있으면 생성이 가능해서 그냥 문자열로 처리해도 무리가 없을 정도이다.
인터페이스명으로 aa.bb.cc.dd.DDD 라고 입력 받았다면 아래와 같이 생성하면 된다.
package aa.bb.cc.dd;
public interface DDD {
}
실제 위와 같은 문자열을 만들어 내는 소스 코드는 다음과 같다.
public String buildInterface(String interfaceName) throws CoreException {
try {
ASTParser parser = ASTParser.newParser(AST.JLS3);
parser.setSource("".toCharArray());
CompilationUnit unit = (CompilationUnit) parser.createAST(null);
unit.recordModifications();
AST ast = unit.getAST();
PackageDeclaration packageDeclaration = ast.newPackageDeclaration();
packageDeclaration.setName(JdtHelper.createPackageName(ast, interfaceName));
unit.setPackage(packageDeclaration);
TypeDeclaration type = ast.newTypeDeclaration();
type.setInterface(true);
type.setName(ast.newSimpleName(interfaceName.substring(interfaceName.lastIndexOf('.') + 1)));
type.modifiers().add(ast.newModifier(Modifier.ModifierKeyword.PUBLIC_KEYWORD));
unit.types().add(type);
Document document = new Document();
TextEdit edits = unit.rewrite(document, javaProject.getOptions(true));
edits.apply(document);
return document.get();
} catch(Exception ex) {
ex.printStackTrace();
throw new CoreException(DialogHelper.createErrorStatus("Interface Build ERROR", ex));
}
}
3. 클래스 생성
클래스 생성은 조금 복잡하다. 인터페이스를 implements도 해주어야 하고, 필드도 추가해야하고, getter/setter 메서드도 만들어야 한다.
시작부분은 인터페이스와 비슷하다.
코드 중간에 있는 BeanPropertyModel 는 클래스는 필드 설정에 관련된 도메인 클래스로 필드명과 fullyQualifiedName으로 저장되어 있는 필드 타입, 그리고 필드 커멘트 등이 포함되어 있다.
public String buildClass(String className, String classComment, String interfaceName, List properties) throws CoreException {
try {
ASTParser parser = ASTParser.newParser(AST.JLS3);
parser.setSource("".toCharArray());
CompilationUnit unit = (CompilationUnit) parser.createAST(null);
unit.recordModifications();
AST ast = unit.getAST();
// 패키지명 선언
PackageDeclaration packageDeclaration = ast.newPackageDeclaration();
packageDeclaration.setName(JdtHelper.createPackageName(ast, className));
unit.setPackage(packageDeclaration);
// 타입선언
TypeDeclaration type = ast.newTypeDeclaration();
type.setInterface(false);
type.setName(ast.newSimpleName(className.substring(className.lastIndexOf('.') + 1)));
type.modifiers().add(ast.newModifier(Modifier.ModifierKeyword.PUBLIC_KEYWORD));
// javadoc추가
type.setJavadoc(JdtHelper.createTextJavadoc(ast, classComment, ""));
// 인터페이스 implements
if(interfaceName != null && interfaceName.trim().length() > 0) {
type.superInterfaceTypes().add(ast.newSimpleType(JdtHelper.createTypeName(ast, interfaceName)));
}
// 필드 추가
for(BeanPropertyModel property: properties) {
VariableDeclarationFragment vdf = ast.newVariableDeclarationFragment();
vdf.setName(ast.newSimpleName(property.getName()));
FieldDeclaration fd = ast.newFieldDeclaration(vdf);
fd.modifiers().add(ast.newModifier(Modifier.ModifierKeyword.PRIVATE_KEYWORD));
fd.setType(ast.newSimpleType(JdtHelper.createTypeName(ast, property.getType())));
type.bodyDeclarations().add(fd);
fd.setJavadoc(JdtHelper.createTextJavadoc(ast, property.getComment(), property.getName()));
}
// 필드의 getter/setter 메서드 추가.
for(BeanPropertyModel property: properties) {
type.bodyDeclarations().add(JdtHelper.createGetMethodDeclaration(ast, property.getType(), property.getName()));
type.bodyDeclarations().add(JdtHelper.createSetMethodDeclaration(ast, property.getType(), property.getName()));
}
unit.types().add(type);
Document document = new Document();
TextEdit edits = unit.rewrite(document, javaProject.getOptions(true));
edits.apply(document);
return document.get();
} catch(Exception ex) {
ex.printStackTrace();
throw new CoreException(DialogHelper.createErrorStatus("Class Build ERROR", ex));
}
}
그외 유틸리티 함수들
소스 코드 중간에 JdtHelper라는 유틸리티 클래스에서 Javadoc을 생성하거나 getter/setter 등을 생성한다.
Assignment라든지 메서드에 파라미터 전달하는 방법, this에 접근하는 방법등이 아래 소스 코드에 포함되어 있다.
public static Name createPackageName(AST ast, String className) {
String[] identifiers = getPackageIdentifiers(className);
if(identifiers == null || identifiers.length == 0) {
return null;
}
return ast.newName(identifiers);
}
public static Name createTypeName(AST ast, String className) {
String[] identifiers = getClassIdentifiers(className);
if(identifiers == null || identifiers.length == 0) {
return null;
}
return ast.newName(identifiers);
}
public static String[] getPackageIdentifiers(String className) {
if(className == null) {
return null;
}
String[] array = className.split("\\.");
if(array.length < 1) {
return null;
}
String[] result = new String[array.length - 1];
System.arraycopy(array, 0, result, 0, result.length);
return result;
}
public static String[] getClassIdentifiers(String className) {
if(className == null) {
return null;
}
return className.split("\\.");
}
public static String buildGetMethodName(String propName) {
if(propName == null || propName.length() == 0) {
return null;
}
return "get" + Character.toUpperCase(propName.charAt(0)) + propName.substring(1);
}
public static String buildSetMethodName(String propName) {
if(propName == null || propName.length() == 0) {
return null;
}
return "set" + Character.toUpperCase(propName.charAt(0)) + propName.substring(1);
}
@SuppressWarnings("unchecked")
public static MethodDeclaration createGetMethodDeclaration(AST ast, String returnType, String propertyName) {
MethodDeclaration mdGet = ast.newMethodDeclaration();
mdGet.setConstructor(false);
mdGet.modifiers().add(ast.newModifier(Modifier.ModifierKeyword.PUBLIC_KEYWORD));
mdGet.setReturnType2(ast.newSimpleType(JdtHelper.createTypeName(ast, returnType)));
mdGet.setName(ast.newSimpleName(JdtHelper.buildGetMethodName(propertyName)));
Block getBlock = ast.newBlock();
ReturnStatement returnStatement = ast.newReturnStatement();
FieldAccess fieldAccess = ast.newFieldAccess();
ThisExpression thisExpression = ast.newThisExpression();
fieldAccess.setExpression(thisExpression);
fieldAccess.setName(ast.newSimpleName(propertyName));
returnStatement.setExpression(fieldAccess);
getBlock.statements().add(returnStatement);
mdGet.setBody(getBlock);
return mdGet;
}
@SuppressWarnings("unchecked")
public static MethodDeclaration createSetMethodDeclaration(AST ast, String propertyType, String propertyName) {
MethodDeclaration mdSet = ast.newMethodDeclaration();
mdSet.setConstructor(false);
mdSet.modifiers().add(ast.newModifier(Modifier.ModifierKeyword.PUBLIC_KEYWORD));
mdSet.setReturnType2(ast.newPrimitiveType(PrimitiveType.VOID));
mdSet.setName(ast.newSimpleName(JdtHelper.buildSetMethodName(propertyName)));
SingleVariableDeclaration varDecl = ast.newSingleVariableDeclaration();
varDecl.setType(ast.newSimpleType(JdtHelper.createTypeName(ast, propertyType)));
varDecl.setName(ast.newSimpleName(propertyName));
mdSet.parameters().add(varDecl);
Block setBlock = ast.newBlock();
Assignment assignment = ast.newAssignment();
FieldAccess leftHandExpression = ast.newFieldAccess();
ThisExpression thisExpression = ast.newThisExpression();
leftHandExpression.setExpression(thisExpression);
leftHandExpression.setName(ast.newSimpleName(propertyName));
assignment.setLeftHandSide(leftHandExpression);
assignment.setOperator(Assignment.Operator.ASSIGN);
assignment.setRightHandSide(ast.newSimpleName(propertyName));
ExpressionStatement expressStatement = ast.newExpressionStatement(assignment);
setBlock.statements().add(expressStatement);
mdSet.setBody(setBlock);
return mdSet;
}
@SuppressWarnings("unchecked")
public static Javadoc createTextJavadoc(AST ast, String comment, String defaultComment) {
Javadoc javadoc = ast.newJavadoc();
if(comment == null || comment.trim().length() == 0) {
TagElement tag = ast.newTagElement();
TextElement te = ast.newTextElement();
te.setText(defaultComment);
tag.fragments().add(te);
javadoc.tags().add(tag);
} else {
for(String aLine : comment.split(System.getProperty("line.separator"))) {
TagElement tag = ast.newTagElement();
TextElement te = ast.newTextElement();
te.setText(aLine);
tag.fragments().add(te);
javadoc.tags().add(tag);
}
}
return javadoc;
}
@SuppressWarnings("unchecked")
public static void addSingleMemberAnnotation(AST ast, TypeDeclaration type, String annotationType, String literalValue) {
SingleMemberAnnotation anno = ast.newSingleMemberAnnotation();
anno.setTypeName(createTypeName(ast, annotationType));
StringLiteral literal = ast.newStringLiteral();
literal.setLiteralValue(literalValue);
anno.setValue(literal);
List list = (List) type.getStructuralProperty(TypeDeclaration.MODIFIERS2_PROPERTY);
list.add(anno);
}
Resources
http://devdaily.com/java/jwarehouse/eclipse/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverterTest.java.shtml
http://www.ibm.com/developerworks/opensource/library/os-ast/?ca=dgr-lnxw961ASTParser
http://java.chinaitlab.com/Eclipse/38001.html
JDT Dialog에서 타입 validation check
JDT로 클래스나 인터페이스를 생성하거나 메소드나 필드를 생성할 때도 이름이 유효한지 체크할 필요가 있다.
NewTypeWizardPage에서 타입 validation을 하는데 사용하는 클래스는
아래의 소스 코드는 NewTypeWizardPage의 일부이다.
JavaConventionsUtil도 내부적으로 JavaConventions를 사용한다.
JavaConventions는 아래와 같은 메서드들을 제공한다.
상기 메소드들은 모두 org.eclipse.core.runtime.IStatus 으로 리턴한다. IStatus의 getSeverity() 메서드와 getMessage() 메서드를 이용하여 에러 여부와 에러 메시지를 참조할 수 있다.
org.eclipse.jdt.ui.wizards.NewTypeWizardPage 에서 사용하는 방식을 소개하도록 하겠다.NewTypeWizardPage에서 타입 validation을 하는데 사용하는 클래스는
org.eclipse.jdt.core.JavaConventions 클래스다.아래의 소스 코드는 NewTypeWizardPage의 일부이다.
private static IStatus validateJavaTypeName(String text, IJavaProject project) {
if (project == null || !project.exists()) {
return JavaConventions.validateJavaTypeName(text, JavaCore.VERSION_1_3, JavaCore.VERSION_1_3);
}
return JavaConventionsUtil.validateJavaTypeName(text, project);
}
JavaConventionsUtil도 내부적으로 JavaConventions를 사용한다.
JavaConventions는 아래와 같은 메서드들을 제공한다.
- validateCompilationUnitName(String, String, String)
- validateClassFileName(String, String, String)
- validateFieldName(String, String, String)
- validateIdentifier(String, String, String)
- validateImportDeclaration(String, String, String)
- validateJavaTypeName(String, String, String)
- validateMethodName(String, String, String)
- validatePackageName(String, String, String)
- validateClasspathEntry(IJavaProject, IClasspathEntry, boolean)
- validateTypeVariableName(String, String, String)
상기 메소드들은 모두 org.eclipse.core.runtime.IStatus 으로 리턴한다. IStatus의 getSeverity() 메서드와 getMessage() 메서드를 이용하여 에러 여부와 에러 메시지를 참조할 수 있다.
2010년 3월 5일 금요일
JDT에서 Annotation 수정하거나 추가시키기
요구사항: 클래스 Annotation 값 UI로 변경 가능하게 구현
Restful 서비스 클래스에는 아래와 같은 Annotation이 설정된다.
@javax.ws.rs.Produces("")
@javax.ws.rs.Path("")
Produces와 Path Annotation은 name-value pair로 값이 지정되는 일반 annotation이 아니라 안에 문자열로 값만을 저장하고 있는 annotation이다.
JDT에서는 이를 SingleMemberAnnotation이라 부르고, name-value pair로 값이 저장되는 것을 NormalAnnotation이라 부른다.
요구사항은 다름 아니라 Path와 Produces를 UI로 설정하면 해당 빈(클래스)에 annotation값이 변경되도록 하는 것이다.
이미 Produces나 Path annotation이 클래스에 존재하는 경우에는 StringLiteral의 문자열만 변경시켜주면 된다. 하지만 annotation이 없는 경우도 있을 테니 존재하는지 체크하는 로직과 추가시키는 로직도 포함해야 한다.
여기서도 여전히 SimpleName과 QualifiedName 문제가 있다. @javax.ws.rs.Path라고 설정하면 QualifiedName으로 @Path라고 설정하였으면 SimpleName으로 파싱된다.
Annotation을 추가(insert)하는 기능을 찾지 못하였다. 아시는 분은 댓글로 남겨 주세요. 그래서 클래스 시작 위치를 저장해 두었다가 모든 처리가 끝난 후 annotation 문자열 (ex "@javax.ws.rs.Path(\"xxx\")" 등)을 소스 코드에 추가시키도록 처리했다. 이는 모든 replace가 끝나고 document에 반영한 이후에 처리해 주어야 한다.
아래 소스에서
자세한 내용은 아래 소스를 살펴보기 바란다.
Restful 서비스 클래스에는 아래와 같은 Annotation이 설정된다.
@javax.ws.rs.Produces("")
@javax.ws.rs.Path("")
Produces와 Path Annotation은 name-value pair로 값이 지정되는 일반 annotation이 아니라 안에 문자열로 값만을 저장하고 있는 annotation이다.
JDT에서는 이를 SingleMemberAnnotation이라 부르고, name-value pair로 값이 저장되는 것을 NormalAnnotation이라 부른다.
요구사항은 다름 아니라 Path와 Produces를 UI로 설정하면 해당 빈(클래스)에 annotation값이 변경되도록 하는 것이다.
이미 Produces나 Path annotation이 클래스에 존재하는 경우에는 StringLiteral의 문자열만 변경시켜주면 된다. 하지만 annotation이 없는 경우도 있을 테니 존재하는지 체크하는 로직과 추가시키는 로직도 포함해야 한다.
여기서도 여전히 SimpleName과 QualifiedName 문제가 있다. @javax.ws.rs.Path라고 설정하면 QualifiedName으로 @Path라고 설정하였으면 SimpleName으로 파싱된다.
createSingleMemberAnnotation 은 이미 annotation이 존재하는 경우 ASTRewrite에 의해서 replace하기 위해서 새로운 Annotation을 만드는 메서드고 findSingleMemberAnnotation 는 클래스내에 해당 Annotation이 존재하는지를 판단하는 메서드다. Annotation을 추가(insert)하는 기능을 찾지 못하였다. 아시는 분은 댓글로 남겨 주세요. 그래서 클래스 시작 위치를 저장해 두었다가 모든 처리가 끝난 후 annotation 문자열 (ex "@javax.ws.rs.Path(\"xxx\")" 등)을 소스 코드에 추가시키도록 처리했다. 이는 모든 replace가 끝나고 document에 반영한 이후에 처리해 주어야 한다.
아래 소스에서
javaElement.getBuffer().replace(startPos, 0, sb.toString()); 부분이 소스파일에 문자열을 추가시키는 부분이다.자세한 내용은 아래 소스를 살펴보기 바란다.
private void saveConfiguration() {
if (this.selectedBean == null) {
return;
}
String path = selectedBean.getBeanClass().replace('.', '/') + ".java";
try {
ICompilationUnit javaElement = (ICompilationUnit) javaProject
.findElement(new Path(path));
if (javaElement == null || javaElement.exists() == false) {
return;
}
Document document = new Document(javaElement.getSource());
CompilationUnit astRoot = ProjectHelper.parse(javaElement);
astRoot.recordModifications();
ASTRewrite rewrite = ASTRewrite.create(astRoot.getAST());
TypeDeclaration typeDecl = (TypeDeclaration) astRoot.types().get(0);
int startPos = typeDecl.getStartPosition();
SingleMemberAnnotation pathAnno = findSingleMemberAnnotation(
typeDecl, "Path", "javax.ws.rs.Path");
String ln = System.getProperty("line.separator");
StringBuffer sb = new StringBuffer();
if (pathAnno != null) {
SingleMemberAnnotation newPathAnno = createSingleMemberAnnotation(
astRoot, "javax.ws.rs.Path", pathText.getText());
rewrite.replace(pathAnno, newPathAnno, null);
} else {
sb.append("@javax.ws.rs.Path(\"").append(pathText.getText()).append("\")").append(ln);
}
SingleMemberAnnotation producesAnno = findSingleMemberAnnotation(
typeDecl, "Produces", "javax.ws.rs.Produces");
if (producesAnno != null) {
SingleMemberAnnotation newProducesAnno = createSingleMemberAnnotation(
astRoot, "javax.ws.rs.Produces", producesText.getText());
rewrite.replace(producesAnno, newProducesAnno, null);
} else {
sb.append("@javax.ws.rs.Produces(\"").append(producesText.getText()).append("\")").append(ln);
}
TextEdit edits = rewrite.rewriteAST(document, javaElement
.getJavaProject().getOptions(true));
edits.apply(document);
javaElement.getBuffer().setContents(document.get());
if(sb.length() > 0) {
javaElement.getBuffer().replace(startPos, 0, sb.toString());
}
javaElement.save(null, true);
} catch (Exception e) {
DialogHelper.showError(getShell(), e.getMessage());
AmfPlugin.log(e);
}
}
private static SingleMemberAnnotation createSingleMemberAnnotation(
CompilationUnit astRoot, String annotationFullyQualifiedName,
String literalValue) {
SingleMemberAnnotation newAnno = null;
newAnno = astRoot.getAST().newSingleMemberAnnotation();
String[] array = annotationFullyQualifiedName.split("\\.");
if (array.length < 1) {
return null;
}
Name typeName = astRoot.getAST().newName(array);
newAnno.setTypeName(typeName);
StringLiteral annoValue = astRoot.getAST().newStringLiteral();
annoValue.setLiteralValue(literalValue);
newAnno.setValue(annoValue);
return newAnno;
}
public static SingleMemberAnnotation findSingleMemberAnnotation(
TypeDeclaration typeDecl, String simpleName,
String fullyQualifiedName) {
List list = (List) typeDecl
.getStructuralProperty(TypeDeclaration.MODIFIERS2_PROPERTY);
for (Object obj : list) {
if (obj instanceof SingleMemberAnnotation) {
SingleMemberAnnotation cast = (SingleMemberAnnotation) obj;
Name typeName = cast.getTypeName();
if (simpleName.equals(typeName.getFullyQualifiedName())
|| fullyQualifiedName.equals(typeName
.getFullyQualifiedName())) {
return cast;
}
}
}
return null;
}
2010년 3월 2일 화요일
PrimitiveType, SimpleType, QualifiedType, ParameterizedType, ArrayType
JDT로 작업을 하다보면
타입의 하위 클래스로는 아래와 같은 것들이 있다.
클래스 파일을 처리하는 경우라면 타입을 이렇게 복잡하게 정의해서 사용할 필요가 없겠지만,
JDT라서 이렇게 해야겠다는 생각이 든다.
JDT는 소스 파일을 파싱하고, 또 생성해내야 하기 때문이다.
컴파일된 클래스 파일에서 메소드에 대한 정의를 보면 언제나 아래와 같이 하나의 경우만 생긴다.
하지만 자바 소스는 import 여부에 따라서 다음과 같은 소스 코드가 모두 가능하다.
위와 같이 각각의 경우를 모두 다르게 처리해 주어야 하기 때문에 Type이 이처럼 세분화되어 있다.
java.util.List, sample.User와 같이 fullyQualified 명으로 타입이 작성된 경우는 QualifiedType이고,
List, User와 같이 패키지 정보는 생략한 채 클래스명으로만 작성된 경우는 SimpleType이다.
List<User> 와 같이 Generic을 사용한 경우는 ParameterizedType 이다.
String[], String[][]와 같은 타입은 ArrayType 이다.
int, long, void등과 같은 primitive 타입인 경우는 PrimitiveType 이다.
List<? extends User> 와 같이 Generic을 확장한 경우에는 WildcardType 이다.
PrimitiveType과 QualifiedType인 경우에는 toString()을 하면 언제나 클래스 정보와 일치하기 때문에 문제가 되지 않는다. 하지만 SimpleName이 오히려 이름과는 달리 상황을 매우 복잡하게 만든다.
JDT의 경우 메소드의 리턴타입이나 파라미터에 대한 타입을 Type 으로 처리한다.
메소드를 호출하는 소스 코드를 생성하는 경우에 메서드의 리턴타입이나 파라미터 타입은 메소드를 구현한 클래스에서 import 한 것이기 때문에 SimpleType으로 작성되어 있는 메소드를 호출하는 클래스에서는 오류가 발생할 수도 있다. 우연찮게 호출하는 클래스에서도 import가 되어 있으면 오류가 발생하지 않을 수도 있다.
CallerClass가 CalledClass의 List<User> findUsers(String username) 메서드를 호출하는 소스 코드를 생성하고 싶다면 List, User와 같은 클래스를 import 하는 소스를 추가하거나 java.util.List, sample.User 등과같이 fullyQualified 이름으로 변경해서 소스를 생성해야 한다.
다시 말해서 SimpleType을 QualifiedType으로 변경할 필요가 생긴다는 이야기다.
이와 같은 경우 List를 java.util.List로 User를 sample.User로 변경해야 하는 데 이런 내막을 알고 있는 클래스는 CallerClass가 아니라 CalledClass다. CalledClass에서 작성된 메서드기 때문이다.
그래서 이런 작업을 해주는 JDT 클래스가 바로 org.eclipse.jdt.core.IType이고, String[][] resolveType(String typeName) 메서드를 통해서 원하는 결과를 얻을 수 있다.
resolveType() 메서드의 결과로 2차원 배열이 리턴되는데, 왜 2차원으로 했는지는 좀 의하하지만 typeName으로 "List"라고 값을 넘겨주었다면 result[0][0] = "java.util", result[0][1] = "List"가 리턴된다.
resolve 할 수 없는 경우에는 Null이 리턴되고 resolve 할 수 있는 경우에는 result[0][0] + "." + result[0][1] 로 하면 fullyQualified 이름이 된다.
org.eclipse.jdt.core.dom.Type 때문에 혼란스러웠던 적이 많았다.타입의 하위 클래스로는 아래와 같은 것들이 있다.
- ArrayType
- ParameterizedType
- PrimitiveType
- QualifiedType
- SimpleType
- WildcardType
클래스 파일을 처리하는 경우라면 타입을 이렇게 복잡하게 정의해서 사용할 필요가 없겠지만,
JDT라서 이렇게 해야겠다는 생각이 든다.
JDT는 소스 파일을 파싱하고, 또 생성해내야 하기 때문이다.
컴파일된 클래스 파일에서 메소드에 대한 정의를 보면 언제나 아래와 같이 하나의 경우만 생긴다.
java.util.List<sample.User> findUsers(java.lang.String username)
하지만 자바 소스는 import 여부에 따라서 다음과 같은 소스 코드가 모두 가능하다.
List<User> findUsers(String username)
java.util.List<User> findUsers(java.lang.String username)
List<sample.User> findUsers(String username)
...
위와 같이 각각의 경우를 모두 다르게 처리해 주어야 하기 때문에 Type이 이처럼 세분화되어 있다.
java.util.List, sample.User와 같이 fullyQualified 명으로 타입이 작성된 경우는 QualifiedType이고,
List, User와 같이 패키지 정보는 생략한 채 클래스명으로만 작성된 경우는 SimpleType이다.
List<User> 와 같이 Generic을 사용한 경우는 ParameterizedType 이다.
String[], String[][]와 같은 타입은 ArrayType 이다.
int, long, void등과 같은 primitive 타입인 경우는 PrimitiveType 이다.
List<? extends User> 와 같이 Generic을 확장한 경우에는 WildcardType 이다.
PrimitiveType과 QualifiedType인 경우에는 toString()을 하면 언제나 클래스 정보와 일치하기 때문에 문제가 되지 않는다. 하지만 SimpleName이 오히려 이름과는 달리 상황을 매우 복잡하게 만든다.
JDT의 경우 메소드의 리턴타입이나 파라미터에 대한 타입을 Type 으로 처리한다.
메소드를 호출하는 소스 코드를 생성하는 경우에 메서드의 리턴타입이나 파라미터 타입은 메소드를 구현한 클래스에서 import 한 것이기 때문에 SimpleType으로 작성되어 있는 메소드를 호출하는 클래스에서는 오류가 발생할 수도 있다. 우연찮게 호출하는 클래스에서도 import가 되어 있으면 오류가 발생하지 않을 수도 있다.
CallerClass가 CalledClass의 List<User> findUsers(String username) 메서드를 호출하는 소스 코드를 생성하고 싶다면 List, User와 같은 클래스를 import 하는 소스를 추가하거나 java.util.List, sample.User 등과같이 fullyQualified 이름으로 변경해서 소스를 생성해야 한다.
다시 말해서 SimpleType을 QualifiedType으로 변경할 필요가 생긴다는 이야기다.
이와 같은 경우 List를 java.util.List로 User를 sample.User로 변경해야 하는 데 이런 내막을 알고 있는 클래스는 CallerClass가 아니라 CalledClass다. CalledClass에서 작성된 메서드기 때문이다.
그래서 이런 작업을 해주는 JDT 클래스가 바로 org.eclipse.jdt.core.IType이고, String[][] resolveType(String typeName) 메서드를 통해서 원하는 결과를 얻을 수 있다.
resolveType() 메서드의 결과로 2차원 배열이 리턴되는데, 왜 2차원으로 했는지는 좀 의하하지만 typeName으로 "List"라고 값을 넘겨주었다면 result[0][0] = "java.util", result[0][1] = "List"가 리턴된다.
resolve 할 수 없는 경우에는 Null이 리턴되고 resolve 할 수 있는 경우에는 result[0][0] + "." + result[0][1] 로 하면 fullyQualified 이름이 된다.
typeName이 fullyQualified 명(ex, java.util.List)로 넘겨진다 하더라도 결과는 위와 같다.
ArrayType인 경우에는 getComponentType() 메소드를 통해서 배열 컴포넌트의 타입을 읽어올 수 있다.
String[] 인 경우에 getComponentType은 String이 되고, String[][]인 경우에는 getComponentType은 String[]이 된다. recursive하게 호출하면 fullyQualified명을 얻을 수 있다.
아래의 코드는 타입을 fullyQualifiedName으로 변경하는 메서드다.
이런 기능을 하는 JDT 유틸리티 클래스를 알고 있다면 댓글에 알려주시면 감사..
클래스명으로 IType을 리턴하는 아래 코드를 참조
ArrayType인 경우에는 getComponentType() 메소드를 통해서 배열 컴포넌트의 타입을 읽어올 수 있다.
String[] 인 경우에 getComponentType은 String이 되고, String[][]인 경우에는 getComponentType은 String[]이 된다. recursive하게 호출하면 fullyQualified명을 얻을 수 있다.
아래의 코드는 타입을 fullyQualifiedName으로 변경하는 메서드다.
이런 기능을 하는 JDT 유틸리티 클래스를 알고 있다면 댓글에 알려주시면 감사..
public String toTypeString(IType javaType, Type type) {
if(type.isPrimitiveType()) {
return type.toString();
} else if(type.isSimpleType()) {
if(javaType == null) {
return type.toString();
}
String[][] result = null;
try {
result = javaType.resolveType(type.toString());
} catch (JavaModelException e) {
AmfPlugin.log(e);
return type.toString();
}
if(result != null && (result.length > 0 && result[0].length == 2)) {
return result[0][0] + "." + result[0][1];
}
return type.toString();
} else if(type.isQualifiedType()) {
return ((QualifiedType) type).getName().getFullyQualifiedName();
} else if(type.isParameterizedType()) {
StringBuffer sb = new StringBuffer();
ParameterizedType parameterizedType = (ParameterizedType) type;
sb.append(toTypeString(javaType, parameterizedType.getType()));
sb.append("<");
List typeArgs = parameterizedType.typeArguments();
for(int i = 0, len = typeArgs.size(); i <> 0) {
sb.append(", ");
}
sb.append(toTypeString(javaType, (Type) typeArgs.get(i)));
}
sb.append(">");
return sb.toString();
} else if(type.isArrayType()) {
ArrayType arrayType = (ArrayType) type;
Type componentType = arrayType.getComponentType();
// 다차원 배열인 경우에는 component 타입이 또다시 ArrayType으로 리턴되어 recursive하게 호출된다.
StringBuffer sb = new StringBuffer();
sb.append(toTypeString(javaType, componentType));
sb.append("[]");
return sb.toString();
} else {
return type.toString();
}
}
클래스명으로 IType을 리턴하는 아래 코드를 참조
public static IType findType(IJavaProject javaProject, String classname) throws JavaModelException {
ICompilationUnit cu = findJavaElement(javaProject, classname);
return cu.getType(classname.substring(classname.lastIndexOf('.') + 1));
}
public static ICompilationUnit findJavaElement(IJavaProject javaProject, String classname) throws JavaModelException {
String javaPath = classname.replace('.', '/') + ".java";
return (ICompilationUnit) javaProject.findElement(new Path(javaPath));
}
MethodDeclaration 데이터 처리
org.eclipse.jdt.core.dom.MethodDeclaration은 Eclipse의 JDT에 정의된 클래스로, 이클립스에서 자바 소스 코드를 작성할 때 메서드에 대한 여러가지 정보를 읽어 내는데 많이 사용되는 클래스다.
1. 파라미터 정보 읽기
1. 파라미터 정보 읽기
List parameters = method.parameters();
for(int i = 0, len = parameters.size(); i <> 0) {
sb.append(", ");
}
SingleVariableDeclaration var = (SingleVariableDeclaration) parameters.get(i);
sb.append(var.getType()).append(" ");
sb.append(var.getName());
}
2010년 3월 1일 월요일
JDT를 이용하여 java 파일에 필드 및 getter/setter 추가하기
요구사항:
Wizard에서 필드 타입, 필드명, getter/setter 생성 여부, 주석등을 입력받아서 해당 java 파일에 관련 소스 코드를 생성한다.
필드 추가는
getter / setter 메소드는 이클립스에서 제공하는
IType.getField() 메소드는 필드가 존재하지 않는 경우에도 값이 리턴이 되므로
getterFields, setterFields, accessors는 null로 전달하면 NullPointerException이 발생하므로 주의해야 한다.
getter, setter 메소드를 생성하기 전에 반드시 field먼저 추가해야 에러가 발생하지 않는다.
Wizard에서 필드 타입, 필드명, getter/setter 생성 여부, 주석등을 입력받아서 해당 java 파일에 관련 소스 코드를 생성한다.
필드 추가는
org.eclipse.jdt.core.IType 의 IField createField(String contents, IJavaElement sibling, boolean force, IProgressMonitor monitor) throws JavaModelException; 메서드를 이용하여 생성하였다.getter / setter 메소드는 이클립스에서 제공하는
org.eclipse.jdt.internal.corext.codemanipulation.AddGetterSetterOperation 를 이용하였다.
public static void addProperty(IProgressMonitor monitor, ICompilationUnit unit, IType type, String propertyType, String propertyName, boolean generateGetter, boolean generateSetter, String comment) throws JavaModelException, CoreException{
IField field = type.getField(propertyName);
if(field.exists() == false) {
StringBuffer sb = new StringBuffer();
if(comment == null || comment.trim().length() == 0) {
comment = propertyName;
}
sb.append(buildFieldComment(comment));
String contents = "private " + propertyType + " " + propertyName + ";";
sb.append(contents);
field = type.createField(sb.toString(), null, true, monitor);
}
IField[] getterFields = new IField[0];
IField[] setterFields = new IField[0];
IField[] accessors = new IField[0];
if(generateGetter) {
getterFields = new IField[1];
getterFields[0] = field;
}
if(generateSetter) {
setterFields = new IField[1];
setterFields[0] = field;
}
CodeGenerationSettings settings= JavaPreferencesSettings.getCodeGenerationSettings(unit.getJavaProject());
// getter/setter의 주석을 추가시킨다.
settings.createComments = true;
AddGetterSetterOperation op = new AddGetterSetterOperation(type, getterFields, setterFields, accessors, new RefactoringASTParser(AST.JLS3).parse(type.getCompilationUnit(), true), null, null, settings, true, false);
op.run(monitor);
}
public static String buildFieldComment(String comment) {
StringBuffer sb = new StringBuffer();
sb.append("/**").append(lineDelimiter);
if(comment == null || comment.trim().length() == 0) {
sb.append(" *");
} else {
for(String aLine : comment.split(lineDelimiter)) {
sb.append(" * ").append(aLine).append(lineDelimiter);
}
}
sb.append(" */");
sb.append(lineDelimiter);
return sb.toString();
}
IType.getField() 메소드는 필드가 존재하지 않는 경우에도 값이 리턴이 되므로
if(field == null) 이렇게 비교하면 안된다.getterFields, setterFields, accessors는 null로 전달하면 NullPointerException이 발생하므로 주의해야 한다.
getter, setter 메소드를 생성하기 전에 반드시 field먼저 추가해야 에러가 발생하지 않는다.
IType의 getMethod 사용
getMethod 시 parameterTypeSignatures
org.eclipse.jdt.code.IType 인터페이스에 정의된
하지만 이 타입정보라는 것이 int, String 과 같은 클래스 정보를 넘겨줘야 하는 것이 아니다.
JDT에서 사용하는 signature 문자열로 넘겨 줘야 하는데
타입정보를 아래와 같이 설정해 주어야 한다.
엄밀히 말하면 TypeSignature는 클래스의 bytecode에 저장되는 메소드의 파리미터 타입 지정 방식이다.
파라미터의 클래스 정보를 위와 같이 변경하기에 다소 불편함이 있어서 JDT에서는 이와 관련된 유틸리티 클래스를 제공한다.
다음은 사용 예다.
파라미터 클래스를 Signature로 변경하는 부분을 참조하기 바란다.
org.eclipse.jdt.code.IType 인터페이스에 정의된
IMethod getMethod(String name, String[] parameterTypeSignatures) 사용하여 메소드를 가져오기 위해서는 파라미터 타입 정보를 넘겨줘야 한다.하지만 이 타입정보라는 것이 int, String 과 같은 클래스 정보를 넘겨줘야 하는 것이 아니다.
JDT에서 사용하는 signature 문자열로 넘겨 줘야 하는데
org.eclipse.jdt.core.Signature api doc 을 참조하면 자세한 내용을 볼 수 있다.타입정보를 아래와 같이 설정해 주어야 한다.
엄밀히 말하면 TypeSignature는 클래스의 bytecode에 저장되는 메소드의 파리미터 타입 지정 방식이다.
TypeSignature ::=
"B" // byte
| "C" // char
| "D" // double
| "F" // float
| "I" // int
| "J" // long
| "S" // short
| "V" // void
| "Z" // boolean
| "T" + Identifier + ";" // type variable
| "[" + TypeSignature // array X[]
| "!" + TypeSignature // capture-of ?
| ResolvedClassTypeSignature
| UnresolvedClassTypeSignature
파라미터의 클래스 정보를 위와 같이 변경하기에 다소 불편함이 있어서 JDT에서는 이와 관련된 유틸리티 클래스를 제공한다.
org.eclipse.jdt.internal.core.util.Util 클래스다.다음은 사용 예다.
파라미터 클래스를 Signature로 변경하는 부분을 참조하기 바란다.
...
MethodDeclaration method = (MethodDeclaration) element;
String name = method.getName().getFullyQualifiedName();
List parameters = method.parameters();
String[] parameterTypeSignatures = new String[parameters.size()];
for(int i = 0; i < parameterTypeSignatures.length; i++) {
SingleVariableDeclaration var = (SingleVariableDeclaration) parameters.get(i);
parameterTypeSignatures[i] = Util.getSignature(var.getType());
}
IMethod javaMethod = type.getMethod(name, parameterTypeSignatures);
...
2010년 2월 26일 금요일
JFace Wizard Dialog 열기
Eclipse Plugin으로 Wizard를 만드는 경우에는 File --> New 에 Wizard가 자동으로 등록되기 때문에 Wizard Dialog가 어떻게 작동하는지 알지 못해도 상관이 없지만,
버튼을 클릭하는 경우와 같이 별도의 이벤트를 받아서 다이얼로그를 띄워야 할 경우에는 프로그램적으로 WizardDialog를 띄워줘야 한다.
물론 Wizard와 Wizard 안에 포함되어야 하는 WizardPage 들은 WizardDialog가 어떤 방식으로 호출되든 상관없이 미리 개발되어 있어야 한다.
아래의 예제는 Eclipse에서 가장 많이 사용하게 되는 Wizard 중에 하나인 클래스 생성 Wizard를 이벤트를 받아서 띄우는 예제다.
참고로 WizardPage는 createControl 메소드 내에서 반드시 setControl() 메소드를 호출해 주어야 한다.
이렇게 하지 않으면 다이얼로그 창을 생성할 때 에러가 발생한다.
버튼을 클릭하는 경우와 같이 별도의 이벤트를 받아서 다이얼로그를 띄워야 할 경우에는 프로그램적으로 WizardDialog를 띄워줘야 한다.
물론 Wizard와 Wizard 안에 포함되어야 하는 WizardPage 들은 WizardDialog가 어떤 방식으로 호출되든 상관없이 미리 개발되어 있어야 한다.
아래의 예제는 Eclipse에서 가장 많이 사용하게 되는 Wizard 중에 하나인 클래스 생성 Wizard를 이벤트를 받아서 띄우는 예제다.
NewClassCreationWizard wizard = new NewClassCreationWizard(project, isInter, value);
WizardDialog dialog = new WizardDialog(PDEPlugin.getActiveWorkbenchShell(), wizard);
dialog.create();
SWTUtil.setDialogSize(dialog, 400, 500);
if (dialog.open() == Window.OK) {
return wizard.getQualifiedName();
}
if (dialog.open() == Window.OK) { 안에는 Wizard의 Finish 버튼이 클릭된 후 처리되어야 하는 로직을 작성해 주면 된다.참고로 WizardPage는 createControl 메소드 내에서 반드시 setControl() 메소드를 호출해 주어야 한다.
이렇게 하지 않으면 다이얼로그 창을 생성할 때 에러가 발생한다.
@Override
public void createControl(Composite parent) {
Composite container = new Composite(parent, SWT.NONE);
setControl(container);
}
JDT 타입 선택 다이얼로그
JDT로 메서드를 추가하는 다이얼로그 개발 중 메서드의 리턴 타입이나, 파라미터 타입이 클래스 일 경우에 프로젝트에서 접근 가능한 클래스목록 중에서 선택해야 하는 요구사항이 있었다.
해결 방법부터 간단히 이야기 하면
parent: shell 이다. 별로 신경 쓸게 없다.
context: 이건 뭔지 잘 모르겠다.
scope: 이건 검색 범위라고 보면 된다. 특정 프로젝트내에서 검색하고 싶으면
style: 선택할 타입의 유형을 결정할 수 있다. 클래스, 인터페이스, enum, annotation 들 중에서 선택할 수 있으며, 이 모든 것들을 선택할 수도 있다.
multipleSelection: true이면 두 개 이상 타입을 선택할 수 있다. false이면 하나만 지정할 수 있다. true로 선택해야 할 경우가 거의 없다.
filter: 텍스트 입력 컴포넌트와 찾기 버튼 형태의 조합으로 UI가 구성될 경우에 유용하다. 만약 텍스트 필드에 "java.io.IO"라고 입력되어 있으며 filter가 그 값으로 지정되어 있으면, 다이얼로그는 java.io.IO로 시작하는 타입만 목록에 보여주게 된다. 지정할 필요가 없는 경우에는 ""으로 설정한다.
extension: extension은 다이얼로그를 세세하게 제어할 때 필요하다. 필요없는 경우에는 지정하지 않으면 된다. 예를 들면 출력되는 타입 목록 중에서 java.lang.Throwable을 상속한 인터페이스와 구현한 클래스들만 선택가능하게 할 경우에 extension의 validator를 이용하면 처리할 수 있다.
다음은 사용 예다.
일반적인 경우에 TypeSelectionExtension 를 자주 사용하지 않기 때문에 위의 예제에서는 null로 설정하였지만, 실제 코드에서는 ext를 아래와 같이 구현하였다. Throwable 인터페이스의 하위 인터페이스와 구현 클래스만 지정 가능하도록 구현된 예제다.
해결 방법부터 간단히 이야기 하면
JavaUI.createTypeDialog() 메서드를 이용하면 된다. createTypeDialog(Shell parent, IRunnableContext context, IJavaSearchScope scope, int style,
boolean multipleSelection, String filter, TypeSelectionExtension extension)
parent: shell 이다. 별로 신경 쓸게 없다.
PDEPlugin.getActiveWorkbenchShell() context: 이건 뭔지 잘 모르겠다.
PlatformUI.getWorkbench().getProgressService() scope: 이건 검색 범위라고 보면 된다. 특정 프로젝트내에서 검색하고 싶으면
SearchEngine.createJavaSearchScope(new IJavaElement[] { javaProject }) 이런 식으로 지정해 주면 된다. javaProject는 JDT로 현재 작업하고 있는 Java 프로젝트를 의미하는 것으로 일반 프로젝트 정보를 알고 있을 때 JavaCore.create(project) 라고 해주면 생성할 수 있다.style: 선택할 타입의 유형을 결정할 수 있다. 클래스, 인터페이스, enum, annotation 들 중에서 선택할 수 있으며, 이 모든 것들을 선택할 수도 있다.
- IJavaElementSearchConstants.CONSIDER_CLASSES,
- IJavaElementSearchConstants.CONSIDER_INTERFACES,
- IJavaElementSearchConstants.CONSIDER_ANNOTATION_TYPES,
- IJavaElementSearchConstants.CONSIDER_ENUMS,
- IJavaElementSearchConstants.CONSIDER_ALL_TYPES,
- IJavaElementSearchConstants.CONSIDER_CLASSES_AND_INTERFACES,
- IJavaElementSearchConstants.CONSIDER_CLASSES_AND_ENUMS
중에서 선택하여 쓸 수 있다.multipleSelection: true이면 두 개 이상 타입을 선택할 수 있다. false이면 하나만 지정할 수 있다. true로 선택해야 할 경우가 거의 없다.
filter: 텍스트 입력 컴포넌트와 찾기 버튼 형태의 조합으로 UI가 구성될 경우에 유용하다. 만약 텍스트 필드에 "java.io.IO"라고 입력되어 있으며 filter가 그 값으로 지정되어 있으면, 다이얼로그는 java.io.IO로 시작하는 타입만 목록에 보여주게 된다. 지정할 필요가 없는 경우에는 ""으로 설정한다.
extension: extension은 다이얼로그를 세세하게 제어할 때 필요하다. 필요없는 경우에는 지정하지 않으면 된다. 예를 들면 출력되는 타입 목록 중에서 java.lang.Throwable을 상속한 인터페이스와 구현한 클래스들만 선택가능하게 할 경우에 extension의 validator를 이용하면 처리할 수 있다.
다음은 사용 예다.
int scopeType = IJavaElementSearchConstants.CONSIDER_ALL_TYPES;
String filter = "";
TypeSelectionExtension ext = null;
SelectionDialog dialog =
JavaUI.createTypeDialog(PDEPlugin.getActiveWorkbenchShell(),
PlatformUI.getWorkbench().getProgressService(),
SearchEngine.createJavaSearchScope(new IJavaElement[] { javaProject }),
scopeType,
false,
filter,
ext);
dialog.setTitle(PDEUIMessages.GeneralInfoSection_selectionTitle);
if (dialog.open() == Window.OK) {
IType type = (IType) dialog.getResult()[0];
throwsTableViewer.add(type.getFullyQualifiedName('$'));
}
일반적인 경우에 TypeSelectionExtension 를 자주 사용하지 않기 때문에 위의 예제에서는 null로 설정하였지만, 실제 코드에서는 ext를 아래와 같이 구현하였다. Throwable 인터페이스의 하위 인터페이스와 구현 클래스만 지정 가능하도록 구현된 예제다.
TypeSelectionExtension ext = new TypeSelectionExtension() {
@Override
public ISelectionStatusValidator getSelectionValidator() {
ISelectionStatusValidator validator =
new ISelectionStatusValidator() {
@Override
public IStatus validate(Object[] selection) {
if(selection != null && selection.length == 1 &&
selection[0] instanceof BinaryType) {
BinaryType binaryType = (BinaryType) selection[0];
Class selectedClass =
ProjectHelper.loadClass(javaProject, binaryType.getFullyQualifiedName('$'));
if(java.lang.Throwable.class.isAssignableFrom(selectedClass)) {
return Status.OK_STATUS;
}
}
String message = "Only Throwable classes are available";
return new Status(IStatus.ERROR, IRuntimeConstants.PI_RUNTIME, IStatus.OK, message, null);
}
};
return validator;
}
};
2010년 2월 25일 목요일
JDT를 이용하여 java 파일에 메소드 추가하기
Eclipse 에는 Java -> Editor -> Templates에 코드 템플릿을 등록해 두고 Ctrl+Space를 누르면 소스 코드가 생성되는 마법을 부리기도 하지만, 어떤 경우에는 반복적으로 단순 코드를 붙혀 넣어야 하는 개발자 운명에 처하기도 한다.
Velocity를 이용해서 자동 생성하는 프로그램도 개발하기도 하지만 JDT라는 멋진 도구를 이용해서 간단한 (?) 플러그인을 개발하면 이런 운명을 피해갈 수도 있다.
아래의 코드 샘플은 인터페이스와 구현 클래스에 메서드를 추가시키는 샘플코드다.
JDT의 AST를 이용하면 좀더 세세하게 메소드의 내부까지 제어할 수 있지만 메서드에 내용을 문자열로 만들어 낼 수만 있다면 아래와 같이 사용할 수도 있다.
메서드를 생성하는 부분은 별도의 dialog로 구현했기 때문에
아래와 같이 작성하면 java 파일의 가장 마지막으로 메서드가 추가된다.
Resources:
http://www.vogella.de/articles/EclipseJDT/article.html
Velocity를 이용해서 자동 생성하는 프로그램도 개발하기도 하지만 JDT라는 멋진 도구를 이용해서 간단한 (?) 플러그인을 개발하면 이런 운명을 피해갈 수도 있다.
아래의 코드 샘플은 인터페이스와 구현 클래스에 메서드를 추가시키는 샘플코드다.
JDT의 AST를 이용하면 좀더 세세하게 메소드의 내부까지 제어할 수 있지만 메서드에 내용을 문자열로 만들어 낼 수만 있다면 아래와 같이 사용할 수도 있다.
메서드를 생성하는 부분은 별도의 dialog로 구현했기 때문에
int getSample() 라는 메서드를 인터페이스에 추가한다고 가정한 코드이다.아래와 같이 작성하면 java 파일의 가장 마지막으로 메서드가 추가된다.
if(interfaceName.length() > 0) {
IType interfaceType = ProjectHelper.getJavaType(javaProject, interfaceName);
interfaceType.createMethod("int getSample();", null, true, monitor);
}
boolean override = interfaceName.length() > 0;
IType beanType = ProjectHelper.getJavaType(javaProject, bean.getBeanClass());
beanType.createMethod("@Override\n public int getSample() { return 0; }", null, true, monitor);
Resources:
http://www.vogella.de/articles/EclipseJDT/article.html
피드 구독하기:
글 (Atom)