Eclipse Helios 릴리즈

Java 2010. 6. 24. 22:36

안드로이드에 빠져 살아서 잊고 있었는데, 올해도 어김없이 약속한 날짜에 eclipse 새버전이 출시되었다.

로고 색깔이 바뀌어서 이제 SUN이 사라졌으니 Eclipse가 SUN의 지위를 차지하려는 것인가 하고 생각했는데, 그건 아닌 것 같다. 실행할 때 뜨는 로고는 여전히 보라색이다.

테스트 결과 페이지를 살펴보며 내가 저질러놓은 코드를 반성하게 된다. 나는 언제쯤 저런 수준의 코드를 만들 수 있을까?
Posted by lispholic
,

요즘 안드로이드 프로그램을 만드는데, 테스트를 위해 회사 동료의 모토로이를 잠시 빌렸다(감사^^). 모토로이에 프로그램을 넣으려면 윈도우에서는 USB 드라이버를 깔아야 하지만, 리눅스에서는 그럴 필요없이 몇 가지 설정만 하면 된다.

일단 /etc/udev/rules.d/51-android.rules 파일을 만들고 아래 내용을 넣는다.

SUBSYSTEM=="usb", SYSFS{idVendor}=="22b8", MODE="0666"
SUBSYSTEM=="usb|usb_device", SYSFS{idVendor}=="22b8", MODE="0660", GROUP="plugdev"
SUBSYSTEM=="usb|usb_device", ATTR{idVendor}=="22b8", ATTR{idProduct}=="0c02", SYMLINK+="android_adb"
SUBSYSTEM=="usb|usb_device", ATTR{idVendor}=="22b8", ATTR{idProduct}=="0c01", SYMLINK+="android_fastboot"

idVendor22b8는 모토롤라 제조사 코드이다. 다른 회사 코드는 여기 에서 확인해보면 된다. 이 파일에 실행권한을 주고 자신을 usb,plugdev 그룹에 넣는다. 이렇게 하면 root가 아니어도 모토로이에 접근할 수 있다.

모토로이에 프로그램을 띄워보니 역시 에뮬레이터와는 크게 차이가 있다. 이것저것 테스트해보고 싶지만 내 것이 아니다보니 마음대로 할 수가 없다. (모토로이 너무 비싸...)

참고한 곳

Posted by lispholic
,
안드로이드 개발에 대해 다루고 있는 책이다. 원서(Android Wireless Application Development)가 2009년 9월에 출간되었고 이 책이 11월에 출간되었으니 최신의 내용을 담고 있어야 하는데, 불행하게도 한창 번역중일 때 안드로이드 1.6버전이 나오더니, 출간할 때가 다 되어서 2.0 이 나왔다 (게다가 현재 최신 버전은 2.1 ... ). 안드로이드가 너무 빨리 발전하는 바람에 피해를 봤다고 할 수 있다. (이는 다른 안드로이드 책도 마찬가지)

번역기간은 짧지만 번역하신 분이 상당히 공을 들였다. 이 책을 평가한 글 중에 "발로 번역했다"는 내용도 있는데, 곳곳에 있는 역주나 (코드를 일일이 직접 실행하고 테스트해보지 않았으면 그런 역주를 달 수 없다) 역자분이 직접 추가한 부록 등을 보면 절대 저런 소리는 하지 못할 거다.

아마 저런 평가를 한 것은 용어를 한글화해서 그런 것 같은데, 안드로이드가 국내에 소개되기 시작한지 얼마안되었기 때문에 이런 시도는 바람직하다고 생각한다. (하지만 이미 대세는 기운듯...)

혹시 번역상태가 궁금하면 http://occamsrazr.net/tt/223 에 역자분이 직접 추가한 부록을 올려놓았으니 읽어보면 될 것이다.

책은 안드로이드 개발에 대해 전반적인 내용을 다루고 있어 그리 깊이 있는 내용은 없다. 제목대로 처음 시작할 때 적합하다.

책 정보 보러가기


Posted by lispholic
,

이 내용은 Controlling IBus with DBusSharp (by hindlet)를 참고했다. 훌륭한 글을 쓴 저자에게 감사드린다. (Thanks for great article !!!)

Java에서 한글입력기의 한/영 상태를 바꾸는 기능이 필요했는데, 이것 저것 알아보던 중 IBus 를 사용하면 D-Bus 를 이용하여 한/영 상태를 바꾸는게 가능하다는 것을 알아냈다.

먼저 IBus를 설치하여 적절하게 설정한다. 그 다음 D-Bus Java 바인딩을 받아서 설치한다. (이 과정은 알아서...)

이제 코딩을 시작해야 하는데 먼저 org.freedesktop.dbus.DBusConnection 객체를 얻어야 한다. 그러려면 IBus 데몬의 주소를 알야야 하는데 $HOME/.config/ibus/bus 디렉토리 아래에 파일이 하나 있을 것이다. 이 파일을 열어 IBUS_ADDRESS값을 읽으면 된다. 그냥 프로퍼티 파일이므로 java.util.Properties 로 읽으면 된다. 이렇게 읽은 주소를 이용하여 org.freedesktop.dbus.DBusConnection 객체를 얻는다.

DBusConnection conn = DBusConnection.getConnection("unix:abstract=/tmp/dbus-wSEDsZTDyV,guid=aaaaaaaaaaaaaaaaaaaaaaaaaaaa") ;

이 커넥션으로 이제 IBus 객체를 얻어올 수 있는데, IBus의 이름과 패스를 알아야 한다. 자세히는 모르겠지만 패스는 D-Bus시스템 상에서 IBus가 가지는 가상의 주소인 것 같다. IBus 의 이름은 org.freedesktop.IBus , 패스는 /org/freedesktop/IBus이다. 이것을 이용하여 IBus를 나타내는 자바 객체를 얻을 수 있다.

??? ibus = (???)conn.getRemoteObject("org.freedesktop.IBus", "/org/freedesktop/IBus") ;

타입명 부분에 ???를 써 놓았다. 저 자리에 무엇을 넣어야 하나? 정답은 해당 리모트 객체가 구현한 인터페이스를 직접 선언하여 사용해야 한다는 것이다. 그럼 그 인터페이스는 어떻게 알 수 있나? 이런 경우를 위해 D-Bus에는 인트로스펙션 API가 있는데 IBus역시 이 기능을 이용하여 어떤 인터페이스를 구현했는지 정보를 제공한다. 다음처럼 하면 XML 형태로 정보를 볼 수 있다.

Introspectable ibus_intro = (Introspectable)conn.getRemoteObject("org.freedesktop.IBus", "/org/freedesktop/IBus" , Introspectable.class) ;
System.out.println(ibus_intro.Introspect());

org.freedesktop.DBus.Introspectable을 이용하여 필요한 정보를 볼 수 있다. 위 코드를 실행하면 아래와 같은 결과가 나온다.

<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
  <interface name="org.freedesktop.DBus.Introspectable">
    <method name="Introspect">
      <arg name="data" direction="out" type="s"/>
    </method>
  </interface>
  <interface name="org.freedesktop.IBus">
    <method name="GetAddress">
      <arg name="address" direction="out" type="s"/>
    </method>
    <method name="CreateInputContext">
      <arg name="name" direction="in" type="s"/>
      <arg name="context" direction="out" type="o"/>
    </method>
    <method name="CurrentInputContext">
      <arg name="name" direction="out" type="s"/>
    </method>
    <method name="RegisterComponent">
      <arg name="components" direction="in" type="v"/>
    </method>
    <method name="ListEngines">
      <arg name="engines" direction="out" type="av"/>
    </method>
    <method name="ListActiveEngines">
      <arg name="engines" direction="out" type="av"/>
    </method>
    <method name="Exit">
      <arg name="restart" direction="in" type="b"/>
    </method>
    <method name="Ping">
      <arg name="data" direction="in" type="v"/>
      <arg name="data" direction="out" type="v"/>
    </method>
    <signal name="RegistryChanged">
    </signal>
  </interface>
</node>

이 정보를 토대로 정의해야하는 인터페이스는 org.freedesktop.IBus이며 GetAddress등 여러 메소드가 있다는 것을 알 수 있다. 인터페이스의 메소드를 모두 선언할 필요는 없고 필요한 것만 선언하면 된다. 여기서는 현재의 입력컨텍스트 정보를 얻는 CurrentInputContext 메소드만 있으면 되므로 인터페이스를 다음처럼 선언한다.

package org.freedesktop;
import org.freedesktop.dbus.DBusInterface;
// 모든 DBus 인터페이스는 DBusInterface를 상속받아야 한다.
public interface IBus extends DBusInterface {
    public String CurrentInputContext();
}

이 인터페이스를 이용하여 현재 입력컨텍스트 정보를 얻으면 되는데, CurrentInputContext 메소드를 호출하면 현재 입력컨텍스트의 패스가 나온다. 이 패스를 이용하여 입력컨텍스트 객체를 얻으면 된다.

IBus ibus = (IBus)conn.getRemoteObject("org.freedesktop.IBus", "/org/freedesktop/IBus") ;
String context_path = ibus.CurrentInputContext() ;
??? context = (???) conn.getRemoteObject("org.freedesktop.IBus", context_path) ;

또 다시 물음표가 등장했다. 저기에는 무슨 타입을 써야하나? 이번에도 입력컨텍스트 패스에 인트로스펙션 API를 적용해보면 알 수 있다.

String context_path = ibus.CurrentInputContext() ;
        
Introspectable ctx_intro = (Introspectable) conn.getRemoteObject("org.freedesktop.IBus", context_path , Introspectable.class) ;
System.out.println(ctx_intro.Introspect());
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
  <interface name="org.freedesktop.DBus.Introspectable">
    <method name="Introspect">
      <arg name="data" direction="out" type="s"/>
    </method>
  </interface>
  <interface name="org.freedesktop.IBus.InputContext">
    <method name="ProcessKeyEvent">
      <arg name="keyval" direction="in" type="u"/>
      <arg name="keycode" direction="in" type="u"/>
      <arg name="state" direction="in" type="u"/>
      <arg name="handled" direction="out" type="b"/>
    </method>
    <method name="SetCursorLocation">
      <arg name="x" direction="in" type="i"/>
      <arg name="y" direction="in" type="i"/>
      <arg name="w" direction="in" type="i"/>
      <arg name="h" direction="in" type="i"/>
    </method>
    <method name="FocusIn"/>
    <method name="FocusOut"/>
    <method name="Reset"/>
    <method name="Enable"/>
    <method name="Disable"/>
    <method name="IsEnabled">
      <arg name="enable" direction="out" type="b"/>
    </method>
    <method name="SetCapabilities">
      <arg name="caps" direction="in" type="u"/>
    </method>
    <method name="SetEngine">
      <arg name="name" direction="in" type="s"/>
    </method>
    <method name="GetEngine">
      <arg name="desc" direction="out" type="v"/>
    </method>
    <method name="Destroy"/>
    <signal name="CommitText">
      <arg name="text" type="v"/>
    </signal>
    <signal name="Enabled"/>
    <signal name="Disabled"/>
    <signal name="ForwardKeyEvent">
      <arg name="keyval" type="u"/>
      <arg name="keycode" type="u"/>
      <arg name="state" type="u"/>
    </signal>
    <signal name="UpdatePreeditText">
      <arg name="text" type="v"/>
      <arg name="cursor_pos" type="u"/>
      <arg name="visible" type="b"/>
    </signal>
    <signal name="ShowPreeditText"/>
    <signal name="HidePreeditText"/>
    <signal name="UpdateAuxiliaryText">
      <arg name="text" type="v"/>
      <arg name="visible" type="b"/>
    </signal>
    <signal name="ShowAuxiliaryText"/>
    <signal name="HideAuxiliaryText"/>
    <signal name="UpdateLookupTable">
      <arg name="table" type="v"/>
      <arg name="visible" type="b"/>
    </signal>
    <signal name="ShowLookupTable"/>
    <signal name="HideLookupTable"/>
    <signal name="PageUpLookupTable"/>
    <signal name="PageDownLookupTable"/>
    <signal name="CursorUpLookupTable"/>
    <signal name="CursorDownLookupTable"/>
    <signal name="RegisterProperties">
      <arg name="props" type="v"/>
    </signal>
    <signal name="UpdateProperty">
      <arg name="prop" type="v"/>
    </signal>
  </interface>
</node>

정말 많은 메소드가 있지만 필요한 것은 현재 한글입력 상태인지 확인하는 IsEnabled , 한글입력 상태로 바꾸는 Enable, 영어입력 상태로 바꾸는 Disable 뿐이다. 인터페이스 이름이 org.freedesktop.IBus.InputContext이므로 위에서 만든 IBus 인터페이스 안에 정의해야 한다.

package org.freedesktop;
import org.freedesktop.dbus.DBusInterface;
// 모든 DBus 인터페이스는 DBusInterface를 상속받아야 한다.
public interface IBus extends DBusInterface {
    public String CurrentInputContext();
    
    public static interface InputContext extends DBusInterface {
        public boolean IsEnabled() ;
        public void Enable() ;
        public void Disable() ;
    }
}

이제 복잡한 것은 다 끝났다. 필요한 메소드를 차례대로 호출하면 된다. 시험삼아 SWT 프로그램을 만들었다. 텍스트 입력창에서 쉬프트키를 누르면 한영상태가 바뀐다. (Swing은 왠지 IBus와 붙지않는 듯하여 테스트하지 못했다. 뭔가 방법이 있겠지만 귀찮아서...)

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.freedesktop.IBus;
import org.freedesktop.IBus.InputContext;
import org.freedesktop.dbus.DBusConnection;
import org.freedesktop.dbus.exceptions.DBusException;
public class IBusTester {
    private static final String ADDR =
        "unix:abstract=/tmp/dbus-wSEDsZTDyV,guid=aaaaaaaaaaaaaaaaaaaaaaaaaaaa" ;
    
    public static void toggleIBus() throws DBusException {
        DBusConnection conn = DBusConnection.getConnection(ADDR) ;
        IBus ibus = (IBus)conn.getRemoteObject("org.freedesktop.IBus", "/org/freedesktop/IBus") ;
        String context_path = ibus.CurrentInputContext() ;
        InputContext context = (InputContext) conn.getRemoteObject("org.freedesktop.IBus", context_path) ;
        
        if(context.IsEnabled()) {
            context.Disable() ;
        }
        else {
            context.Enable() ;
        }
        conn.disconnect() ;
    }
    
    public static void main (String [] args) {
        Display display = new Display ();
        Shell shell = new Shell (display);
        shell.setLayout( new RowLayout() ) ;
        Text text = new Text (shell, SWT.BORDER);
        text.setSize(200, 50) ;
        
        text.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                if (e.keyCode == SWT.SHIFT) {
                    try {
                        toggleIBus() ;
                    }
                    catch (Throwable e1) {
                        e1.printStackTrace();
                    }
                }
            }
        }) ;
        
        
        shell.pack ();
        shell.open ();
        while (!shell.isDisposed ()) {
            if (!display.readAndDispatch ()) display.sleep ();
        }
        display.dispose ();
    }
}

몇가지 주의사항을 언급하자면

  • IBUS_ADDRESS 값은 IBus데몬이 새로 시작할 때 마다 바뀐다. 실제 코드에는 저렇게 하드 코딩하지 말고 파일을 읽어 처리하도록 수정해야 한다.
  • 커넥션을 연 후에는 필요없으면 닫아야 한다.
  • 커넥션을 닫은 후에는 해당 커넥션에서 얻어놓은 객체는 정상동작하지 않는다.
  • 텍스트 입력영역에 포커스가 있는 상태가 아닌데 IBus.CurrentInputContext()를 호출하면 예외가 발생한다.
  • 일단 InputContext 객체를 얻은 후에는 커넥션을 닫지 않는한 텍스트 입력영역에 포커스가 있지 않아도 입력상태를 바꾸는게 가능하다. 하지만 시스템 트레이에 상태가 바로 보이지는 않으며 텍스트 입력영역에 다시 포커스를 맞추면 최종 상태가 표시된다.
Posted by lispholic
,

Scala 주의사항

Scala 2009. 11. 22. 21:37

클래스 객체 사용하기

Java 에서는 클래스 객체를 쓰려면 String.class 같은 식으로 쓴다. Scala는 이 문법을 쓰지 않고 아래처럼 한다.

val cls = classOf[String] ;

타입명을 []안에 쓰게하여 언어의 다른 요소와 일관성을 맞추려 한 것 같다. 하지만 개인적으로는 Java문법이 더 편하다.

캐스팅하기, 특정 타입인지 검사하기

Java에서는 캐스팅할 때 C와 같은 문법을 쓰는데 Scala는 아래처럼 한다.

// 특정 객체를 String 타입으로 캐스팅
val str = someObj.asInstanceOf[String];

캐스팅 문법은 Scala가 더 좋다.

객체가 특정 타입인지 검사할 때 Java는 instanceof연산자를 쓴다.

if( someObj instanceof Integer ) {
    Integer i = (Integer)someObj ;
    System.out.println(i*i) ;
}
else {
    // Integer가 아닌 경우 처리
}

Scala에는 instanceof에 해당하는게 없고 패턴매칭을 쓴다.

someObj match {
    case i:Int => println (i*i)
    case _ => // Int가 아닌 경우 처리
}

이것 역시 Scala가 더 낫다.

Annotation

아래의 Java 어노테이션을 Scala에서 쓴다고 해보자.

package test;
import java.lang.annotation.*;
@Target({ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.RUNTIME)
public @interface ConsAnnotation {
}

이 어노테이션을 아래 class의 생성자에 붙이려면 아래처럼 하면 될 것 같다.

package test
@ConsAnnotation
class TargetClass1 (num:Int){
}

컴파일해보면 잘 된다. 하지만 아래코드를 실행해보면

package test
object Run1 {
    def main(args:Array[String]) {
        val cls = classOf[TargetClass1] ;
        println(cls + " ==> " + cls.getAnnotation(classOf[ConsAnnotation])) ;
        cls.getConstructors().foreach{ cons =>
            println(cons + " ==> " + cons.getAnnotation(classOf[ConsAnnotation])) ;
        }
    }
}

뭔가 문제가 있음을 알 수 있다.

        class test.TargetClass1 ==> @test.ConsAnnotation()
        public test.TargetClass1(int) ==> null
        

이렇게 하면 생성자가 아니라 클래스에 어노테이션이 붙는다. 생성자에 붙이려면 아래처럼 클래스명과 매개변수 리스트 사이에 어노테이션을 넣어야 한다. 그리고 어노테이션에 매개변수를 넣지 않더라도 ()를 꼭 붙여야 한다.(안 붙이면 컴파일 오류가 난다.)

package test
class TargetClass2 @ConsAnnotation()(num:Int ){
}
        class test.TargetClass2 ==> null
        public test.TargetClass2(int) ==> @test.ConsAnnotation()
        

이 어노테이션은 생성자에만 붙도록 되어있음에도 클래스에 붙일 수 있다는 것은 뭔가 이상하다. 내가 확인해본 바로는 scalac는 어노테이션의 Target설정을 확인하지 않는다고 한다.

Posted by lispholic
,