2010년 2월 26일 금요일

JFace Wizard Dialog 열기

Eclipse Plugin으로 Wizard를 만드는 경우에는 File --> New 에 Wizard가 자동으로 등록되기 때문에 Wizard Dialog가 어떻게 작동하는지 알지 못해도 상관이 없지만,
버튼을 클릭하는 경우와 같이 별도의 이벤트를 받아서 다이얼로그를 띄워야 할 경우에는 프로그램적으로 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로 메서드를 추가하는 다이얼로그 개발 중 메서드의 리턴 타입이나, 파라미터 타입이 클래스 일 경우에 프로젝트에서 접근 가능한 클래스목록 중에서 선택해야 하는 요구사항이 있었다.
해결 방법부터 간단히 이야기 하면 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일 목요일

ElementTreeSelectionDialog 사용하기

org.eclipse.ui.dialogs 에는 이클립스에서 제공하는 유용한 다이얼로그들이 많다.
ElementTreeSelectionDialog는 트리 형태의 데이터를 출력한 후 사용자가 선택을 반영할 수 있는 다이얼로그다.
ContentProvider와 LabelProvider를 생성해 주어야 한다.
아래와 같이 간단하게 TreeSelection 다이얼로그를 사용할 수 있다.




SqlMapConfigParser parser = new SqlMapConfigParser();

SqlMapConfig sqlMapConfig =
parser.parse(ProjectHelper.getSqlMapConfigFile(javaProject.getProject()).getContents());


ElementTreeSelectionDialog dialog =
new ElementTreeSelectionDialog(getShell(), new SqlMapLabelProvider(),
new SqlMapContentProvider());

dialog.setTitle("dialog title");
dialog.setMessage("dialog message");

dialog.setInput(sqlMapConfig);
if(dialog.open() == Window.OK) {
for(Object selected: dialog.getResult()) {
if(selected instanceof SqlMap.IbatisQuery) {
ibatisStatements.add((SqlMap.IbatisQuery)selected);
}
}
tableViewer.refresh();
}





ElementTreeSelectionDialog를 사용할 때 트리의 최하단 노드만 선택할 수 있게 하거나, 선택된 노드들의 validation을 선택하게 만들면 사용하기 편할때가 있다. 이럴 경우에는 ISelectionStatusValidator 를 이용하면 된다. 아래의 코드를 참조하기 바란다.



dialog.setValidator(new ISelectionStatusValidator() {

@Override
public IStatus validate(Object[] selection) {
for(Object obj : selection) {
if (!(obj instanceof MethodDeclaration)) {
return new Status(IStatus.ERROR, IRuntimeConstants.PI_RUNTIME, IStatus.ERROR, "Only methods can be selected.", null);
}
}
return Status.OK_STATUS;
}
});




Dialog의 창 크기를 명시적으로 지정하고자 한다면 org.eclipse.pde.internal.ui.util.SWTUtil 를 이용하면 된다. SWTUtil 이름으로 된 클래스는 한 두개가 아니라서 fullyQualifiedName으로 알아두지 않으면 종종 귀찮은 일이 생긴다. SWTUtil.setDialogSize() 를 호출하기 전에 반드시 dialog.create() 먼저 해 줘야 NullPointerException이 발생하지 않는 다는 것도 알아두기 바란다.



dialog.create();

SWTUtil.setDialogSize(dialog, 600, 400);






Resources:
http://blog.cypal-solutions.com/2008/07/selection-dialogs-in-eclipse.html

JDT를 이용하여 java 파일에 메소드 추가하기

Eclipse 에는 Java -> Editor -> Templates에 코드 템플릿을 등록해 두고 Ctrl+Space를 누르면 소스 코드가 생성되는 마법을 부리기도 하지만, 어떤 경우에는 반복적으로 단순 코드를 붙혀 넣어야 하는 개발자 운명에 처하기도 한다.
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

XMLBeans에서 parsing할 때 namespace가 지정되지 않았을 때 처리

XMLBeans로 XSD파일을 이용해서 XmlObject를 generation 하여 사용하면
XML 파싱(unmarshalling)과 빌딩(marshalling)에 관한 코드를 별도로 작성할 필요가 없다.

아래와 같이 ant파일을 작성하여 실행하면 xsd 파일에 해당하는 java 클래스를 생성하여 jar파일로 build 된다.




<project name="project" default="xmlobject-generate-jar" basedir=".">

<property environment="env"></property>

<path id="xmlbeans.classpath">
<fileset dir="${env.XMLBEANS_HOME}">
<include name="lib/*.jar">
</include></fileset>
</path>

<taskdef name="xmlbean" classname="org.apache.xmlbeans.impl.tool.XMLBean" classpathref="xmlbeans.classpath">

<target name="xmlobject-generate-jar">
<xmlbean classgendir="gensrc" download="true" javasource="1.5" destfile="ibatis-xmlobject.jar" classpathref="xmlbeans.classpath">
<fileset dir="xsd/ibatis">
<include name="*.xsd">
</include></fileset>
</xmlbean>

<!-- <delete dir="genSrc"><br /> -->
</target>

</taskdef></project>




하지만 이렇게 생성된 클래스파일로 작업을 하게 되면 입력되는 XML 파일이 xsd에서 정의된 namespace로 작성되어 있어야 한다.
namespace가 생략된 채 단순히 태그명(localPart)만으로 작성된 XML 엘리멘트들은 전혀 다른 엘리멘트로 인식하기 때문에 파싱도중 에러가 발생한다.

그래서 네임스페이스가 정의되지도 않고, 네임스페이스 prefix도 포함되어 있지 않은 태그에 대해서는 기본 네임스페이스를 지정해 준 후에 파싱을 해야 한다.



XmlOptions xmlOptions = new XmlOptions();
xmlOptions.setUseDefaultNamespace();
Map namespaces = new HashMap();
namespaces.put("", "http://ibatis.apache.org/dataMapper");
//xmlOptions.setSaveImplicitNamespaces(namespaces); // 저장할 때
xmlOptions.setLoadSubstituteNamespaces(namespaces);

SqlMapConfigDocument document = SqlMapConfigDocument.Factory.parse(inputStream, xmlOptions);




위와 같이 작성하면 기본 네임스페이스가 지정되기 때문에 파싱할 때 정의된 엘리멘트를 찾을 수 없다는 에러가 발생하지 않는다.


Resources
http://xmlbeans.apache.org/
http://www.niclabs.cl/plain/DTE_OpenLibs/src/cl/nic/dte/examples/GeneraFacturaCompleta.java
http://www.niclabs.cl/plain/DTE_OpenLibs/src/cl/nic/dte/examples/GeneraFactura.java

Ant 에서 시스템 환경변수 접근하기

Ant로 build script를 작성하다 보면 특정 환경 변수로부터 값을 읽어야 하는 경우가 있다.
아래의 예제는 XMLBEANS_HOME 라는 환경 변수에 상대경로의 lib 폴더에 있는 모든 jar 파일을
xmlbeans.classpath로 설정하는 예제다.
간단한 예제라서 별도의 설명은 생략하도록 한다.




<property environment="env"></property>

<path id="xmlbeans.classpath">
<fileset dir="${env.XMLBEANS_HOME}">
<include name="lib/*.jar"></include>
</fileset>
</path>





Resources:
How do I access environment variables within Ant

SWT Table Cell Editor 사용

SWT Table의 기본 셀 에디터는 TextCellEditor 다
셀이 선택되었을 때 Text 입력 모드로 변경이 되고, 엔터키가 입력되거나 포커스를 잃게 되었을때 Text안의 문자열로 값이 설정된다.
리스트에서 선택하는 경우에는 ComboCellEditor를 사용하면 된다.

나는 메소드의 파라미터에 대한 타입을 지정하는 셀에디터가 필요했다.
Primitive 타입은 Combo를 이용하여 선택할 수 있도록 하고,
클래스는 찾기 버튼을 클릭할때 JDT의 JavaUI.createTypeDialog() 메소드를 이용하여 별도의 다이얼로그에서 선택할 수 있도록 해야했다.
또한 배열타입을 고려하여 사용자가 직접 값을 입력할 필요도 있었다. (이부분은 Combo 의 스타일만 적용하면 된다.)

이와 같은 요구사항을 종합해 보면 메소드 파라미터 목록은 테이블의 각 아이템으로 설정이 가능하며,
파라미터 타입은 별도의 CellEditor를 구현해야만 하고, 파라미터명은 TextCellEditor를 이용하여 구현하면 된다.
타입을 설정하기 위한 CellEditor는 Combo와 버튼이 있어야 하고 Combo에는 Primitive 타입을 선택할 수 있도록 하고 버튼이 클릭되면 타입(Class, Interface, Enum 등)을 선택할 수 있는 다이얼로그 창이 열린 후 선택된 값으로 설정되도록 해야 한다.


1. 테이블의 각 셀에 셀 에디터 설정하기


protected void setupViewer(TableViewer tableViewer) {
Table table = tableViewer.getTable();

table.addListener(SWT.MeasureItem, measureListener = new Listener() {

@Override
public void handleEvent(Event event) {
event.height = event.gc.getFontMetrics().getHeight() * 2;
}
});
TableViewerColumn column = null;

column = new TableViewerColumn(tableViewer, SWT.NONE);
column.getColumn().setText("parameter type");
column.getColumn().setWidth(200);

EditingSupport editingSupport = new ParameterEditingSupport(viewer, MethodParameterColumn.PARAMETER_TYPE);
column.setLabelProvider(new ParameterLabelProvider(MethodParameterColumn.PARAMETER_TYPE));
column.setEditingSupport(editingSupport);


column = new TableViewerColumn(tableViewer, SWT.NONE);
column.getColumn().setText("name");
column.getColumn().setWidth(200);
column.setLabelProvider(new ParameterLabelProvider(MethodParameterColumn.PARAMETER_NAME));
column.setEditingSupport(new ParameterEditingSupport(viewer, MethodParameterColumn.PARAMETER_NAME));

table.setLinesVisible(true);
table.setHeaderVisible(true);
}

2. EditingSupport 클래스 구현하기

class ParameterEditingSupport extends EditingSupport {
private final MethodParameterColumn column;
private boolean editable;

public ParameterEditingSupport(ColumnViewer viewer, MethodParameterColumn column) {
super(viewer);
this.column = column;
init();
}

@Override
protected boolean canEdit(Object element) {
return this.editable;
}

@Override
protected CellEditor getCellEditor(Object element) {

if (element instanceof MethodParameter) {
final MethodParameter parameter = (MethodParameter) element;

switch(column) {
case PARAMETER_TYPE:
final CellEditor cellEditor = new TypeSelectionDialogCellEditor(viewer.getTable(), javaProject);
cellEditor.addListener(new ICellEditorListener() {

@Override
public void applyEditorValue() {
System.out.println("applyEditorValue()");
System.out.println("value: " + cellEditor.getValue());
parameter.setType((String)cellEditor.getValue());
manager.refresh();
}

@Override
public void cancelEditor() {
System.out.println("cancelEditor()");
}

@Override
public void editorValueChanged(boolean oldValidState,
boolean newValidState) {
System.out.println("editorValueChanged: " + oldValidState + ", " + newValidState);
}

});

return cellEditor;
case PARAMETER_NAME:
this.editable = true;
return new TextCellEditor(viewer.getTable());
}

}
return new TextCellEditor(viewer.getTable());
}

@Override
protected Object getValue(Object element) {
if (element instanceof MethodParameter) {
MethodParameter cast = (MethodParameter) element;
return MethodParameterManagerHelper.getValue(cast, column);
}
return "NULL";
}

@Override
protected void setValue(Object element, Object value) {
if (element instanceof MethodParameter) {
MethodParameter cast = (MethodParameter) element;

MethodParameterManagerHelper.setValue(value, cast, column);
getViewer().update(element, MethodParameterColumn.getColumnProperties());
getViewer().refresh(element);
return;
}
throw new IllegalArgumentException("invalid element: " + element);
}

private void init() {
switch(column) {
case PARAMETER_TYPE:
this.editable = true;
break;
case PARAMETER_NAME:
this.editable = true;
break;
}
}
}


3. CellEditor 구현하기
각 셀에 대한 에디터는 getCellEditor() 메소드에 의해서 결정된다. parameterType에 대한 CellEditor는 위의 코드에서 TypeSelectionDialogCellEditor 이다.
클래스에 대한 소스 코드 양이 많지 않으니 모두 포함하도록 한다.


package com.archnal.amf.ui.wizards;


import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.ui.IJavaElementSearchConstants;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jface.viewers.DialogCellEditor;
import org.eclipse.jface.window.Window;
import org.eclipse.pde.internal.ui.PDEPlugin;
import org.eclipse.pde.internal.ui.PDEUIMessages;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.SelectionDialog;

public class TypeSelectionDialogCellEditor extends DialogCellEditor {
private static final String[] PRIMITIVE_PARAMETER_TYPES = {
"boolean",
"byte",
"char",
"double",
"float",
"int",
"long",
"short"
};
private IJavaProject javaProject;
private Combo combo;
private SelectionListener selectionListener;


public TypeSelectionDialogCellEditor(Composite parent, IJavaProject javaProject) {
super(parent);
this.javaProject = javaProject;
}

@Override
public void dispose() {
combo.removeSelectionListener(selectionListener);

super.dispose();
}
@Override
protected Control createContents(Composite cell) {
// return super.createContents(cell);

Composite composite = new Composite(cell, getStyle());
composite.setBackground(cell.getBackground());

GridLayout layout = new GridLayout(1, false);
layout.marginWidth = 0;
layout.marginHeight = 0;
composite.setLayout(layout);

GridData gd = new GridData(GridData.FILL_HORIZONTAL);

combo = new Combo(composite, SWT.NONE);
combo.setItems(PRIMITIVE_PARAMETER_TYPES);
combo.setLayoutData(gd);

combo.addSelectionListener(selectionListener = new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {

markDirty();

String value = combo.getText();
doSetValue(value);

fireApplyEditorValue();
}
});
return composite;
}

@Override
protected Object openDialogBox(Control cellEditorWindow) {
// TODO Auto-generated method stub
try {
final int scopeType = IJavaElementSearchConstants.CONSIDER_ALL_TYPES;
// String filter = getReturnType();
String filter = getValue() == null ? "" : getValue().toString();

// filter = filter.substring(filter.lastIndexOf(".") + 1); //$NON-NLS-1$
// SelectionDialog dialog = JavaUI.createTypeDialog(PDEPlugin.getActiveWorkbenchShell(), PlatformUI.getWorkbench().getProgressService(), SearchEngine.createWorkspaceScope(), scopeType, false, filter);
SelectionDialog dialog = JavaUI.createTypeDialog(PDEPlugin.getActiveWorkbenchShell(), PlatformUI.getWorkbench().getProgressService(), SearchEngine.createJavaSearchScope(new IJavaElement[] { javaProject }), scopeType, false, filter);
dialog.setTitle(PDEUIMessages.GeneralInfoSection_selectionTitle);
if (dialog.open() == Window.OK) {
IType type = (IType) dialog.getResult()[0];
return type.getFullyQualifiedName('$');
// returnTypeCombo.setText(type.getFullyQualifiedName('$'));
// entry.setValue(type.getFullyQualifiedName('$'));
// entry.commit();
}
} catch (CoreException e) {
e.printStackTrace();
}
return null;
}

}


위의 소스 코드에서 다음과 같은 사항은 주의해서 볼 필요가 있다.
TypeSelectionDialogCellEditor.openDialogBox()에서
doSetValue() 메소드 호출 후 fireApplyEditorValue() 메소드를 호출하여
셀의 값이 변경되었음을 누군가에게 통지하고 있다.
어딘가에서는 그 값을 변경하여 모델이 되는 파라미터 타입 값을 변경해 주어야 한다.
ParameterEditingSupport 클래스의 getCellEditor() 메소드에 파라미터 타입에 대한 셀 에디터 생성 시 ICellEditorListener를 등록하여 applyEditorValue()메소드에서 값이 변경될 때마다 파라미터 타입에 변경해주고 있음을 알 수 있다.

SWT Table cell 높이, 넓이 변경

SWT Table을 이용하여 프로그램을 하다보면 일반적으로 셀 높이를 변경할 필요가 없지만,
셀에디터를 사용하여 셀을 편집해야 할 경우에 콤보박스가 일부 잘려 보이기도 한다.
이런 경우에는 Table의 cell 높이를 늘려 주어야 UI가 보기 좋게 된다.

아래와 같이 코드를 작성하면 테이블의 셀 크기를 변경할 수 있다.

Table table = tableViewer.getTable();

table.addListener(SWT.MeasureItem, measureListener = new Listener() {

@Override
public void handleEvent(Event event) {
event.height = event.gc.getFontMetrics().getHeight() * 2;
}
});
물론 폭도 설정가능하다.


UI 프로그래밍에서 쓸데없이 메모리를 사용하지 않도록 등록된 리스너는 반드시 제거시키는 것도 잊지 말아야 한다.

viewer.getTable().removeListener(SWT.MeasureItem, measureListener);


Resources:
http://lekkimworld.com/2008/03/27/setting_table_row_height_in_swt.html

http://www.eclipse.org/articles/article.php?file=Article-CustomDrawingTableAndTreeItems/index.html

2010년 2월 24일 수요일

클래스 - 인터페이스 및 상위클래스 구현여부 체크

Object 가 특정 타입(클래스 및 인터페이스)을 상속받았거나 구현한 경우를 체크하기 위해서는

if(obj instanceof $TargetClass.class) {
}

위와 같이 체크하면 된다.

그런데 특정 클래스가 다른 인터페이스를 구현했거나 상속받았는지를 체크하기 위해서는 어떻게 하면 될까?
상위클래스들을 모조리(java.lang.Object 가 될때까지) 찾아다니면서 구현한 Interface들을 확인하면 될것도 같은데 왠지 세련되지 않은 것 같다.

간만에 Java API document를 열어서 java.lang.Class 에 구현되어 있는 메소드 목록을 훑어 보던 중
isAssignableFrom 이라는 메소드를 발견했다.

나의 요구사항은 사용자로부터 입력받은 클래스가 java.util.Collection 인터페이스를 (implements)구현한 클래스인지 체크하는 것이었다.

Class clazz = Class.forName(className);
boolean isCollection = java.util.Collection.class.isAssignableFrom(clazz);

위와 같이 사용하면 다음과 같은 의미로 해석된다.
java.util.Collection은 clazz로 assign할 수 있다.
다시 말해서 clazz 클래스는 java.util.Collection 인터페이스를 구현한 클래스다라는 의미가 된다.

className = "java.util.ArrayList"로 설정한 후 테스트해 보길 바란다.

더 좋은 방법이 있으면 댓글로 깨우침을 주면 더 고마울것도 같다. ^^;

2010년 2월 17일 수요일

IBatis + mysql AUTO_INCREMENT column

mysql에서 AUTO_INCREMENT로 PK를 설정하여 사용하는 경우
IBatis에서 Insert 후에 자동 생성 된 PK 값을 리턴받아 처리하는 코드 샘플이다.

--- User Table ---

create table T_USER (
user_id INT NOT NULL AUTO_INCREMENT,
login_id varchar(20) NOT NULL,
login_pwd varchar(20) NOT NULL,
user_name varchar(20),
cell_phone varchar(20),
address varchar(100),
email varchar(100),
PRIMARY KEY (user_id),
UNIQUE login_id (login_id)
);


--- UserSqlMap.xml ---



<insert id="insert" parameterClass="amf1.model.User">
insert into T_USER (login_id, login_pwd, user_name, cell_phone, address, email)
values (#loginId:VARCHAR#, #loginPwd:VARCHAR#, #userName:VARCHAR#,
#cellPhone:VARCHAR#, #address:VARCHAR#, #email:VARCHAR#)
<selectKey keyProperty="userId" resultClass="int">
SELECT LAST_INSERT_ID()
</selectKey>
</insert>




---- UserDaoImpl.java ---



@Override
public int insertWithoutPK(User record) {
Integer userId = (Integer) getSqlMapClientTemplate().insert("T_USER.ibatorgenerated_insert",
record);

record.setUserId(userId);

return userId;
}

Eclipse + IBator : dao, model, sqpmap 파일 생성

이클립스+Spring 기반 프레임웍을 개발 중에 DB 처리를 IBatis를 이용하여 구현하기로 결정을 하고, 개발 생산성을 위해서 IBator를 이용하여, model, dao, sqlmap 파일들을 자동 생성하는 기능을 추가하기로 결정하였다.
UI를 이용하여 Ibator configuration 파일을 생성하고, 플러그인 내부에서 IBator 객체를 생성한 후 generate() 메서드를 호출하는 방식으로 구현하였다. 하지만 아래와 같은 제약사항이 있어, IBator.generate() 메서드는 dao, model 클래스를 merge 시키지 못했다. merge시키지 못했다는 의미는 파일을 생성할 때마다 이전에 dao나 model 클래스를 수정하면 수정한 내용이 사라진다는 이야기다. 매번 새롭게 파일을 생성한다.


실행시 주의사항


  1. ibator는 XML 파일이 있는 경우에 기존 파일을 merge하여 생성한다.

  2. ibator는 기본적으로 자바 파일을 merge하지 않지만 ibator eclipse plugin으로 실행하면 merge 하여 저장된다.



추측컨데 Ibator eclipse plugin은 JDT의 AST 기능으로 구현되었기 때문에 merge가 가능한 것 같다.

따라서 프레임웍에서는 Ibator.generate() 를 실행하지 않고 ibator eclipse plugin의 action을 호출하는 것으로 구현하였다.

Resources:
http://ibatis.apache.org/docs/tools/ibator/

이클립스 galileo plugins


maven: http://m2eclipse.sonatype.org/sites/m2e