'java'에 해당되는 글 9건

  1. 2009.01.09 Click on Touch Screen
  2. 2009.01.04 Java AWT Mouse Click Event의 기원 4
  3. 2007.09.02 Java Enum Type, Obfuscation Problem 2
  4. 2006.10.20 How long is your String?
  5. 2006.10.14 CAFEBABE
자바2009. 1. 9. 22:19

Sun이 작년(?)부터 Java SE for Embedded를 배포하고 있다. 점점 커지는 모바일 시장에서 한 몫 해보려는 모양이다. 지난달에는 심지어 ARM Linux 플랫폼을 위한 Java SE for Embedded 6u10 (early access)도 나왔다. 우연찮은 기회(업무 ^^)로 Java SE for Embedded 6u10 기반 ThinkFree Mobile 개발에 참여하게 되었는데, 몇 가지 주목할 만한 내용들이 있어 기록해둔다. 오늘은 일단 Click on Touch Screen에 대해서만.

일전에 기록해둔 것처럼 AWT의 mouse click event는 AWT native 구현체에서 발생이 되는데, 일반 desktop edition의 구현체는 pressed point와 released point가 동일한 경우에만 click event가 발생된다. 우리가 흔히 사용하는 마우스는, 클릭을 할 때 일부러 흔들지 않는 이상 pressed, released point가 다를 가능성이 희박하다. 따라서 의도대로 적절히 click을 행할 수 있다.

하지만 터치 스크린의 경우, 손가락 끝으로 스크린을 두드려야 되는데, 손가락 끝은 다른 도구(스타일러스 등)에 비해 면적이 넓은데다가 굴곡도 있고, 말랑말랑해서 자칫 pressed point와 released point에 차이가 생기게 마련이다(손톱은 그나마 좀 낫다). 그 차이는, 개인적인 측정에 의하면, 작게는 2~3 pixel부터 많게는 20~30 pixel까지 차이가 난다. 간혹 device driver나 OS가 똑똑하게 보정해 주는 경우도 있는 것 같지만, 그렇지 않은 경우라면 문제가 생길 수 있다.

테스트 결과 EJRE(JRE from Java SE for Embedded 6u10)에서는 x, y 좌표 중 하나라도 4 pixel 이상 차이나면 click event가 발생하지 않는 것으로 확인이 되었다. 4 pixel이라는 threshold는 외부 설정으로 변경이 가능한 것인지 아니면 단순 하드코딩된 값인지는 모르겠으나, 일단 불편한 default 값이긴 하다. 이 때문에 EJRE에서 사용자가 원하는 대로 click을 할 수 있도록 구현하려면 약간의 work-around가 필요하다. 내가 사용했던 방법은 대략 아래와 같이 AWT event를 꼬아버리는 방법이다.

Toolkit.getDefaultToolkit().getSystemEventQueue().push(new EventQuene() {
    @Override
    protected void dispatchEvent(AWTEvent event) {
        boolean dispatch = true;
        if(event instanceof MouseEvent) {
            // create and submit a mouse click event by
            // adjusted threshold about 16 ~ 32 pixels
            // between pressed and released point, and
            // execute some customizations by event id,
            // blah blah blah...
            ...
        }
        
        if(dispatch) {
            super.dispatchEvent(event);
        }
    }
});

물론 이런 식의 work-around는 몸에 해롭다. EJRE가 좀 더 유연해지면 얼렁 지워버리어야지. 그리고 위 코드가 동작했을 때의 결과는 아래 동영상으로 확인.


Easy Clicker from Yonghwan Cho on Vimeo.
Posted by roguebean
자바2009. 1. 4. 17:53

보통 AWT/Swing에서 발생되는 input event는 JVM을 호스팅하고 있는 OS의 native input event를 AWT native 구현체가 Java 객체로 변환하여 전달한 것이다. 그런데 Windows(win32)나 Linux(gtk) API에는 mouse click event가 따로 존재하지 않는다(고 알고 있다). 그러면 Java에서 사용되는 mouse click event는 어디서 생성된 것일까? 아마도 아래 OpenJDK 소스에서 답을 찾을 수 있을 것 같다.

MsgRouting AwtComponent::WmMouseUp(UINT flags, int x, int y, int button)
{
    MSG msg;
    InitMessage(&msg, lastMessage, flags, MAKELPARAM(x, y), x, y);

    SendMouseEvent(java_awt_event_MouseEvent_MOUSE_RELEASED, TimeHelper::getMessageTimeUTC(),
                   x, y, GetJavaModifiers(), clickCount,
                   (GetButton(button) == java_awt_event_MouseEvent_BUTTON3 ?
                    TRUE : FALSE), GetButton(button), &msg);
    ...
    if (!(m_mouseDragState & GetButtonMK(button))) { // No up-button in the drag-state
        SendMouseEvent(java_awt_event_MouseEvent_MOUSE_CLICKED,
                       TimeHelper::getMessageTimeUTC(), x, y, GetJavaModifiers(),
                       clickCount, JNI_FALSE, GetButton(button));
    }
    ...
}

Source:
https://openjdk.dev.java.net/svn/openjdk/jdk/trunk/jdk/src/windows/native/sun/windows/awt_Component.cpp

void
awt_canvas_handleEvent(Widget w, XtPointer client_data,
                       XEvent * event, struct WidgetInfo *winfo,
                       Boolean * cont, Boolean passEvent)
{
    ...

    switch (event->type) {
        ...
        case ButtonRelease:
            ...
            awt_post_java_mouse_event(client_data,
                                      java_awt_event_MouseEvent_MOUSE_RELEASED,
                                      (passEvent == TRUE) ? event : NULL,
                                      event->xbutton.time,
                                      modifiers,
                                      x, y,
                                      (jint) (event->xbutton.x_root),
                                      (jint) (event->xbutton.y_root),
                                      clickCount,
                                      FALSE, 0,
                                      button);

            if (lastPeer == client_data) {
                awt_post_java_mouse_event(client_data,
                                          java_awt_event_MouseEvent_MOUSE_CLICKED,
                                          NULL,
                                          event->xbutton.time,
                                          modifiers,
                                          x, y,
                                          (jint) (event->xbutton.x_root),
                                          (jint) (event->xbutton.y_root),
                                          clickCount,
                                          FALSE, 0,
                                          button);
            }
            ...
    }
}

Source:
https://openjdk.dev.java.net/svn/openjdk/jdk/trunk/jdk/src/solaris/native/sun/awt/canvas.c

그렇다. Release event 발생시에 몇 가지 조건만 맞으면 click event도 발생된다. AWT native 구현이 크게 다르지 않다면 다른 JRE도 대략 이러한 과정으로 click event를 생성하지 않을까 생각된다.

Posted by roguebean
자바2007. 9. 2. 16:00

Java enum type은 obfuscate할 때 주의가 필요하다. java.lang.Class.getEnumConstants()에서 reflection으로 해당 enum type의 'values'라는 메서드를 참조하기 때문이다.

예를 들어 net.rb.lab.TestEnum이라는 java enum type이 다음과 같은 소스코드를 가진다고 하자.

package net.rb.lab;

public enum TestEnum {

    ITEM_A,
    ITEM_B;
    
}

이 소스코드를 compile하면 다음과 같은 byte code outline을 확인할 수 있다.

net.rb.lab.TestEnum
Fields:
    ITEM_A
    ITEM_B
    ENUM$VALUES
Methods:
    <clinit>
    <init>
    values
    valueOf

실제 byte code를 decompile해보면 대략 아래와 같은 코드를 유추해낼 수 있다.
(아래 소스코드는 byte code의 구조파악을 위해 기술된 내용이고, Enum 상속 문제때문에 실제 complie은 안된다.)

package net.rb.lab;

public final class TestEnum extends Enum {

    public static final TestEnum ITEM_A;
    public static final TestEnum ITEM_B;
    private static final TestEnum[] ENUM$VALUES;

    static {
        ITEM_A = new TestEnum("ITEM_A", 0);
        ITEM_B = new TestEnum("ITEM_B", 1);
        ENUM$VALUES = new TestEnum[] { ITEM_A, ITEM_B };
    }
    
    private TestEnum(String name, int ordinal) {
        super(name, ordinal);
    }

    public static TestEnum[] values() {
        TestEnum[] newArray = new TestEnum[ENUM$VALUES.length];
        System.arraycopy(ENUM$VALUES, 0, newArray, 0, ENUM$VALUES.length);
        return newArray;
    }

    public static TestEnum valueOf(String name) {
        return (TestEnum) Enum.valueOf(TestEnum.class, name);
    }

}

보시다시피 enum type의 경우, Java compiler에 의해서 다음과 같이 두 메서드가 자동으로 생성된다.

  • public static TestEnum[] values()
  • public static TestEnum valueOf(String)

이 두 메서드가 바로 문제의 녀석들이다. TestEnum.valueOf(String) 호출은 결국 Class.getEnumConstants() 호출을 야기하는데, 만약 values() 메서드가 obfuscate되었다면 Class.getEnumConstants()는 reflection으로 해당 메서드를 찾을 수 없으므로 null을 return, 결국 JVM은 'TestEnum은 enum type이 아닐세'라며 exception을 던져버린다.

Obfuscation의 목적은 decompile시 의미파악을 어렵게 하기 위함인데, Java enum type의 'values'나 'valueOf'같은 메서드는 이미 공개된 내용이므로 obfuscation 목록에서 제외하는게 문제를 예방하는 데 도움이 될 것 같다.

Posted by roguebean
자바2006. 10. 20. 00:10

Core Java Technologies Tech Tips에 참고할만한 자료가 있어 (첫 번째 주제만) 기록해둠. 친절한 용환씨가 주관적으로 직역, 의역, 오역을 섞어가며 기록한 내용임을 밝힘. :)

원문: Strings - Core Java Technologies Technical Tips (By John O'Conner, August 24, 2006)


문자열의 길이가 얼마나 되는가?
어떤 문자열 입력값이 데이터 필드의 길이제한을 초과하지 않는지 검증하기 위해서는 그 문자열의 길이을 알아야 한다. 데이터베이스의 텍스트 필드는 보통 그 내용의 길이가 제한되므로, 저장하기 전에 문자열의 길이를 확인할 필요가 있다. 어쨌든 프로그래밍을 하다 보면 종종 문자열의 길이를 알아야 하는 경우가 있는데, 보통 String object의 length 메서드로 그 값을 알 수 있다. 대부분의 경우 length 메서드로 문제를 해결할 수 있지만 String object의 길이를 측정하는 방법은 length 메서드가 유일한 것은 아니며, 항상 정확한 것도 아니다.

Java에서 문자열의 길이를 측정하는 방식은 적어도 3가지가 존재한다.

  1. char 단위의 개수 측정방식
  2. 문자단위의 개수 측정방식
  3. byte의 개수 측정방식

char 단위의 개수 측정방식

Java에서는 Unicode 표준으로 문자열을 정의한다. Unicode 표준은 한 때 U+0000에서 U+FFFF까지의 16-bit fixed-width 값으로 문자들을 정의했었다. 'U+' 표기는 16진수의 유효한 Unicode 문자값을 의미한다. Java 언어는 char 타입에 이 fixed-width 표준을 적용했고, char 값은 모든 16-bit Unicode 문자를 표현할 수 있었다.

대부분의 프로그래머들은 length 메서드에 익숙하다. 아래 예제코드는 문자열에 포함된 char의 개수를 출력하는데, 예제로 사용된 String object는 몇몇 간단한 문자들과 Java 언어의 '\u' 표기로 정의된 문자들로 이루어져있다. '\u'표기는 Unicode 표준의 'U+' 표기와 유사하게 16-bit char 값을 16진수로 정의한다.

private String testString = "abcd\u5B66\uD800\uDF30";
int charCount = testString.length();
System.out.printf("char count: %d\n", charCount);

length 메서드는 String 객체가 가지고 있는 char의 개수를 반환하므로 예제코드의 출력은 아래와 같다.

char count: 7

문자단위의 개수 측정방식

Unicode 4.0에서 U+FFFF보다 큰 값을 가지는 새로운 문자들을 정의하게 되자, 16-bit의 char 타입으로는 더이상 모든 문자를 표현할 수 없게 되었다. 이에따라 Java 2 Platform, Standard Edition 5.0 (J2SE 5.0)부터는 이런 새로운 Unicode 문자들을 'surrogate pair'라고 부르는, 한 쌍으로 이루어진 16-bit char 값으로 표현하기 시작했다. U+10000부터 U+10FFFF에 이르는 Unicode 문자들을 두 개의 char로 대체하여 표현하는 것이다. 이 범위에 해당하는 문자들을 'supplementary character'라고 부른다.

물론 하나의 char는 여전히 U+FFFF 이하의 Unicode 문자들만 표현가능하지만, surrogate pair 형태를 사용해 supplementary character를 표현할 수 있다. Surrogate pair의 첫번째 char는 U+D800~U+DBFF 범위의 값을 가지고, 두번째 char는 U+DC00~U+DFFF 범위의 값을 가진다. Unicode 표준은 이 두 범위의 문자값들을 surrogate pair를 위해 할당해두었으며, surrogate pair를 U+FFFF보다 큰 문자값으로 매핑하는 알고리즘을 정의하고 있다. Surrogate pair를 이용하면 Unicode 표준의 어떠한 문자도 표현이 가능하다. 이러한 16-bit 표현법을 'UTF-16'이라고 부르며, Java에서는 Unicode 문자들을 표현하기 위해 UTF-16 방식을 사용한다. 따라서 char 타입은 UTF-16 코드단위이며 항상 의미있는 Unicode 문자를 표현하는 것은 아니다.

String의 length 메서드는 char 개수만을 반환하기 때문에 supplementary character의 개수를 제대로 파악할 수 없다. 다행히 J2SE 5.0 API는 String의 codePointCount(int beginIndex, int endIndex)라는 새로운 메서드를 제공한다. 이 메서드는 인자로 전달된 두 지점 사이에 존재하는 Unicode 문자의 개수를 반환한다. 측정 시작지점과 종료지점을 의미하는 beginIndex와 endIndex는 문자단위가 아닌, 코드단위 또는 char의 위치를 가리킨다. codePointCount 메서드가 반환하는 값은 length 메서드가 반환하는 값과 항상 같지는 않다. 즉, 문자열에 surrogate pair가 존재하는 경우, length 메서드와 codePointCount 메서드는 서로 다른 값을 반환하게 된다. Surrogate pair는 하나의 문자코드(code point)를 가리키지만, 두 개의 char 단위로 구성되기 때문이다.

codePointCount 메서드를 이용하여 문자열이 가지고 있는 Unicode 문자코드의 개수를 확인해보자.

private String testString = "abcd\u5B66\uD800\uDF30";
int charCount = testString.length();
int characterCount = testString.codePointCount(0, charCount);
System.out.printf("character count: %d\n", characterCount);

위 예제코드의 출력은 다음과 같다.

character count: 6

예제코드에서 사용된 testString 변수는 두 개의 흥미로운 문자를 가지고 있는데, 하나는 '배움'을 의미하는 한자 '学'이고, 또 하나는 GOTHIC LETTER AHSA라는 문자이다. '学'이라는 한자는 Unicode 문자코드가 U+5B66이며 textString에 포함된 '\u5B66'에 해당한다. Ahsa는 surrogate pair로 표현되며 testString의 '\uD800\uDF30'에 해당한다. testString의 ahsa는 하나의 Unicode 문자코드이지만 두 개의 char 값으로 표현되었고, 따라서 textString 전체의 문자코드 개수는 7이 아니라 6이 된다.

Byte의 개수 측정방식

문자열이 몇 byte로 이루어져 있는가? 그 답은 사용된 character set의 byte-oriented 인코딩에 따라 다르다. 데이터베이스에 입력할 문자열의 길이가 그 제한을 초과하지 않는지 검증하는 경우 문자열에 사용된 byte가 몇 개인 지 확인할 필요가 있다. String에 정의된 getBytes 메서드는 Unicode 문자로 이루어진 문자열을 byte-oriented 인코딩으로 변환하고 그 결과를 byte 배열 형태로 반환한다. UTF-8은 다른 대부분의 byte-oriented 인코딩과는 달리 모든 Unicode 문자코드를 정확하게 표현할 수 있다.

아래 예제코드는 문자열을 byte 배열로 변환한다.

byte[] utf8 = null;
int byteCount = 0;
try {
utf8 = str.getBytes("UTF-8");
byteCount = utf8.length;
} catch (UnsupportedEncodingException ex) {
ex.printStackTrace();
}
System.out.printf("UTF-8 Byte Count: %d\n", byteCount);

getBytes 메서드의 인자는 문자열 변환에 사용할 character set을 지칭하며, 생성되는 byte 배열의 길이를 결정하게 된다. UTF-8 인코딩은 하나의 Unicode 문자코드를 1~4개의 코드단위(byte)로 기록한다. 앞선 예제의 문자열에 포함된 'a', 'b', 'c', 'd'는 4 bytes(각 1 byte씩)가 소요되고, 일본어 한자 '学'은 3 bytes, ahsa는 4 bytes가 소요된다. 따라서 예제코드의 결과는 아래와 같이 출력된다.

UTF-8 Byte Count: 11

측정방식에 따른 문자열의 길이
Figure 1. 측정방식에 따른 문자열의 길이

요약

문자열에 supplementary character가 포함되지 않은 경우 length 메서드와 codePointCount 메서드는 항상 같은 값을 반환한다. 하지만 아시아 문자처럼 U+FFFF보다 큰 값의 문자가 포함된 경우에는 두 메서드의 반환값이 달라지게 되므로, 그 용도에 따라 적절한 메서드를 선택해야 한다. 데이터베이스 character set 인코딩이나 직렬화 포맷의 경우, 대부분 UTF-8이 최적의 선택이다. 이런 경우에도 역시 문자열의 길이는 측정방식에 따라 달라지게 되므로, 다양한 측정방식 중 용도에 따라 알맞은 방식을 선택하면 된다.

부가정보

이 기술문서의 내용과 관련하여 보다 많은 정보를 원한다면 아래의 자료들을 살펴보자.

Posted by roguebean
자바2006. 10. 14. 00:29

Java class file은 0xCAFEBABE라는 4bytes의 magic number로 시작된다. 그런데, 이 'CAFEBABE'라는 단어가 class file의 magic number로 선택된 이유는 무엇일까? Java programming language의 original creator 멤버인 Patrick Naughton'재미있고, 독특하면서 외우기 쉬운 (A~F로 이루어진) 단어'이기 때문이었다고 한다. 참고로 두번째 후보단어는 'DEADBABE'였다고 한다.

Java Cafe

재미있는 사실은, Java라는 단어가 Java programming language를 지칭하는 단어로 선택되기도 한참 전에 class file의 magic number가 결정되었다는 것이다.

여담이지만, Patrick Naughton은 Sun Microsystems뿐만 아니라 Starwave, Disney's Disney Internet Group등 여러 회사에서 근무했는데, 1999년에 아동성추행으로 구속되며 몰락(?)의 길을 걷게 되었다. 안습...

참조: Why CAFEBABE?

Posted by roguebean