2010년 4월 9일 금요일

클래스 소스에 Delegate메서드 생성하기-JDT

이클립스에 Delegate 메서드 생성이라는 재미 있는 기능이 있다.
디자인 패턴을 공부해본 사람이라면 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);
List result = 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);
}
}

댓글 없음:

댓글 쓰기