2010년 3월 2일 화요일

PrimitiveType, SimpleType, QualifiedType, ParameterizedType, ArrayType

JDT로 작업을 하다보면 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 유틸리티 클래스를 알고 있다면 댓글에 알려주시면 감사..


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


댓글 없음:

댓글 쓰기