Duy Do’s WebLog

Imagination is more important than knowledge

ByteTokenizer – a Java class util By Me

Vì nhu cầu để decode các gói tin của Yahoo Messenger nên mình viết thư viện ByteTokenize để sử dụng parse mảng các bytes (bytes array). Class ByteTokenizer tương tự như class java.util.StringTokenizer.

——————————————————————————–

In order to decode Yahoo Messenger packet received from Yahoo Messenger server, I’ve coded class ByteTokenizer, as java.util.StringTokenizer class. This class allows an application to break a byte array into tokens.

Trong quá trình sử dụng nếu có góp ý gì thì các bạn post lên đây để mình cải thiện lớp này hiệu quả hơn.

Class ByteTokenizer:
/**
 * @(#)ByteTokenizer.java Sep 23, 2008
 * Copyright (C) 2008 Duy Do. All Rights Reserved.
 */
package com.duydo.util;

import java.util.Enumeration;
import java.util.NoSuchElementException;

/**
 * The byte tokenizer class allows an application to break a byte array into
 * tokens.
 *
 * @author <a href = "mailto:doquocduy@gmail.com">Duy Do</a>
 * @version Sep 23, 2008 12:49:06 PM
 */
public class ByteTokenizer implements Enumeration<Object> {

	private int currentPosition;
	private int newPosition;
	private int maxPosition;

	private byte[] bytes;
	private byte[] delimiters;

	/**
	 * Constructs a bytes array tokenizer for the specified bytes. The bytes in
	 * the <code>delimiters</code> argument are the delimiters for separating
	 * tokens. Delimiter bytes themselves will not be treated as tokens.
	 * <p>
	 * Note that if <tt>delimiters</tt> is <tt>null</tt>, this constructor does
	 * not throw an exception. However, trying to invoke other methods on the
	 * resulting <tt>ByteTokenizer</tt> may result in a
	 * <tt>NullPointerException</tt>.
	 *
	 * @param bytes a bytes array to be parsed
	 * @param delimiters a bytes array delimiters
	 * @exception NullPointerException if bytes is <CODE>null</CODE>
	 */
	public ByteTokenizer(byte[] bytes, byte[] delimiters) {
		this.bytes = bytes;
		this.delimiters = delimiters;
		currentPosition = 0;
		newPosition = -1;
		maxPosition = bytes.length;
	}

	/**
	 * Constructs a bytes array tokenizer for the specified bytes. The byte
	 * <code>delimiter</code> argument is the delimiter for separating tokens.
	 * Delimiter byte itself will not be treated as tokens.
	 * <p>
	 * Note that if <tt>delimiter</tt> is <tt>null</tt>, this constructor does
	 * not throw an exception. However, trying to invoke other methods on the
	 * resulting <tt>ByteTokenizer</tt> may result in a
	 * <tt>NullPointerException</tt>.
	 *
	 * @param bytes a bytes array to be parsed
	 * @param delimiter a byte delimiter
	 * @exception NullPointerException if bytes is <CODE>null</CODE>
	 */
	public ByteTokenizer(byte[] bytes, byte delimiter) {
		this(bytes, new byte[] { delimiter });
	}

	/**
	 * Tests if there are more tokens available from this tokenizer's bytes
	 * array. If this method returns <tt>true</tt>, then a subsequent call to
	 * <tt>nextToken</tt> with no argument will successfully return a token.
	 *
	 * @return <code>true</code> if there are more tokens; <code>false</code>
	 *         otherwise.
	 */
	public boolean hasMoreTokens() {
		newPosition = skipDelimiters(currentPosition);
		return (newPosition < maxPosition);
	}

	/**
	 * Returns the same value as the <code>hasMoreTokens</code> method. It
	 * exists so that this class can implement the <code>Enumeration</code>
	 * interface.
	 *
	 * @return <code>true</code> if there are more tokens; <code>false</code>
	 *         otherwise.
	 * @see java.util.Enumeration
	 * @see com.duydo.util.ByteTokenizer#hasMoreTokens()
	 */
	public boolean hasMoreElements() {
		return hasMoreTokens();
	}

	/**
	 * Returns the next token from this bytes array tokenizer.
	 *
	 * @return the next token from this bytes array tokenizer.
	 * @exception NoSuchElementException if there are no more tokens in this
	 *                tokenizer's bytes array.
	 */
	public Object nexToken() {
		currentPosition = (newPosition >= 0) ? newPosition
				: skipDelimiters(currentPosition);

		// Reset
		newPosition = -1;

		if (currentPosition >= maxPosition) {
			throw new NoSuchElementException(
					"current token position is out of bound.");
		}
		final int startPosition = currentPosition;
		currentPosition = scanToken(currentPosition);
		final int length = currentPosition - startPosition;
		final byte[] token = new byte[length];
		System.arraycopy(bytes, startPosition, token, 0, length);
		return token;
	}

	/**
	 * Returns the same value as the <code>nextToken</code> method, except that
	 * its declared return value is <code>Object</code> rather than
	 * <code>String</code>. It exists so that this class can implement the
	 * <code>Enumeration</code> interface.
	 *
	 * @return the next token in the bytes array.
	 * @exception NoSuchElementException if there are no more tokens in this
	 *                tokenizer's bytes array.
	 * @see java.util.Enumeration
	 * @see com.duydo.util.ByteTokenizer#nextToken()
	 */
	public Object nextElement() {
		return nexToken();
	}

	/**
	 * Calculates the number of times that this tokenizer's
	 * <code>nextToken</code> method can be called before it generates an
	 * exception. The current position is not advanced.
	 *
	 * @return the number of tokens remaining in the bytes array using the
	 *         current delimiter set.
	 * @see com.duydo.util.ByteTokenizer#nextToken()
	 */
	public int countTokens() {
		int tokenNums = 0;
		int position = currentPosition;
		while (position < maxPosition) {
			position = skipDelimiters(position);
			if (position >= maxPosition) {
				break;
			}
			position = scanToken(position);
			tokenNums++;
		}
		return tokenNums;
	}

	/**
	 * Skips delimiters starting from the specified position. Returns the index
	 * of the first non-delimiter byte at or after startPosition.
	 */
	private int skipDelimiters(final int startPosition) {
		if (delimiters == null) {
			throw new NullPointerException("delimiters");
		}
		int position = startPosition;
		while (position < maxPosition) {
			if (isDelimiter(position)) {
				position += delimiters.length;
			}
			break;
		}
		return position;
	}

	/**
	 * Skips ahead from startPosition and returns the index of the next
	 * delimiter byte encountered, or maxPosition if no such delimiter is found.
	 */
	private int scanToken(final int startPosition) {
		int position = startPosition;
		while (position < maxPosition) {
			if (isDelimiter(position)) {
				break;
			}
			position++;
		}
		return position;
	}

	private boolean isDelimiter(final int startPosition) {
		int position = startPosition;
		boolean isDelimiter = true;
		while ((position < maxPosition) && isDelimiter) {
			int nDelimiter = 0;
			while (nDelimiter < delimiters.length) {
				if (bytes[startPosition + nDelimiter] != delimiters[nDelimiter]) {
					isDelimiter = false;
					break;
				}
				nDelimiter++;
			}
			position++;
		}
		return isDelimiter;
	}
}
Cách sử dụng như sau (Usage)
/**
 * @(#)ByteTokenizerTest.java Sep 24, 2008
 * Copyright (C) 2008 Duy Do. All Rights Reserved.
 */
package com.duydo.util;

/**
 * The class description here.
 *
 * @author <a href = "mailto:doquocduy@gmail.com">Duy Do</a>
 * @version Sep 24, 2008 2:21:54 PM
 */
public class ByteTokenizerTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// bytes array to parse
		final byte[] bytes = new byte[] {
				(byte) 0x31, (byte) 0xC0, (byte) 0x80,
				(byte) 0x64, (byte) 0x75, (byte) 0x79, (byte) 0x64, (byte) 0xC0, (byte) 0x80,
				(byte) 0x39
		};
		// delimiters
		final byte[] delimiters = new byte[]{(byte) 0xC0, (byte) 0x80};

		System.out.println("Byte array to parse: " + getHexDump(bytes));

		// Create an instance of ByteTokenizer
		final ByteTokenizer tokenizer = new ByteTokenizer(bytes, delimiters);

		// Count tokens
		final int tonkenNums = tokenizer.countTokens();
		System.out.println("Token numbers: " + tonkenNums);

		// Print all tokens
		byte[] token;
		while(tokenizer.hasMoreTokens()) {
			token = (byte[]) tokenizer.nexToken();
			System.out.println("Byte token: " + getHexDump(token));
		}
	}

	private static String getHexDump(byte[] bytes) {
		final StringBuffer sb = new StringBuffer();
		for(byte b : bytes){
			sb.append(Integer.toHexString(((int)b & 0xff))).append(" ");
		}
		return sb.toString();
	}
}
Kết quả (Output):
Byte array to parse: 31 c0 80 64 75 79 64 c0 80 39
Token numbers: 3
Byte token: 31
Byte token: 64 75 79 64
Byte token: 39

25/09/2008 - Đăng bởi Duy Do | Java | | 12 phản hồi

12 phản hồi »

  1. Dạ em cảm ơn anh. Để em tham khảo thử ;)

    Comment bởi Lê Thanh Sang | 25/09/2008 | Trả lời

  2. bạn có thể post luôn cách catch gói tin của yahoo messenger được không ? tokenize thì phải có source chứ :D :D

    Comment bởi strongdevil | 26/09/2008 | Trả lời

  3. Good Program

    Comment bởi Thiru | 27/09/2008 | Trả lời

  4. @strongdevil:
    Một gói tin của Yahoo Messenger gồm có 2 phần HEADER và DATA. Phần HEADER chứa 20 bytes, phân DATA chứa n bytes tùy thuộc vào độ dài của gói tin.
    Đây là gói tin mình nhận được từ server Yahoo Messenger sau khi gởi yêu cầu thực hiện bắt tay (handshake) cho account duydo_bot.

    59 4D 53 47 00 0D 00 00 00 62 00 57 00 00 00 01 00 44 BA 7E 31 C0 80 64 75 79 64 6F 5F 62 6F 74 C0 80 39 34 C0 80 68 5E 28 79 5E 6F 5E 69 2A 74 5E 6C 2B 31 5E 62 29 2B 6B 2A 28 62 26 28 73 5E 67 5E 77 26 6C 2D 28 73 2B 6E 2D 61 2B 33 26 28 79 29 2F 38 7C 79 2B 68 2A 66 2B 31 2D 7A 2B 64 2A 32 5E 38 29 2B 63 26 6C 2A 66 29 29 C0 80 31 33 C0 80 31 C0 80

    Phân tích:
    HEADER: 59 4D 53 47 00 0D 00 00 00 62 00 57 00 00 00 01 00 44 BA 7E.
    DATA: 31 C0 80 64 75 79 64 6F 5F 62 6F 74 C0 80 39 34 C0 80 68 5E 28 79 5E 6F 5E 69 2A 74 5E 6C 2B 31 5E 62 29 2B 6B 2A 28 62 26 28 73 5E 67 5E 77 26 6C 2D 28 73 2B 6E 2D 61 2B 33 26 28 79 29 2F 38 7C 79 2B 68 2A 66 2B 31 2D 7A 2B 64 2A 32 5E 38 29 2B 63 26 6C 2A 66 29 29 C0 80 31 33 C0 80 31 C0 80

    Bạn để ý phần DATA chứa lặp đi lặp lại cặp byte: C0 80. Đây chính là ký hiệu phân cách giữa các trường trong data. Và cũng chính từ nhu cấu tách này nên ByteTokenizer ra đời.

    Comment bởi Duy Do | 27/09/2008 | Trả lời

  5. @Thiru: Thanks ;-)

    Comment bởi Duy Do | 27/09/2008 | Trả lời

  6. Thực ra dùng NIO (ByteBuffer) hoàn toàn có thể giải quyết các vấn đề mà lớp Byte… ở trên đặt ra .

    Tuy nhiên việc tự viết một lớp cho riêng mình cũng có nhiều điểm hay ;)

    Comment bởi bkitclub_vn | 28/09/2008 | Trả lời

  7. @bkitclub_vn:
    Anh Khoa nhà mình phải không? ;-)
    Viết API cho thằng Yahoo Messenger em dùng MINA, cũng định dùng IoBuffer (wrapper of ByteBuffer)để xử lý vụ này, nhưng quyết định viết cái lớp này để sau này dùng trên Mobile luôn hehe

    Comment bởi Duy Do | 29/09/2008 | Trả lời

  8. Tui ko phải instcode nor ruathaso ;) ,

    btw, format của code ở đây nhìn chữ nhỏ quá, tui nghĩ bồ nên sửa CSS lại cho dễ coi hơn :)

    Comment bởi bkitclub_vn (a.k.a Mediocre_Ninja) | 29/09/2008 | Trả lời

  9. @bkitclub_vn: Không hiểu sao Duy sửa bài này không được.
    Bồ biết cả instcode và ruathaso, vậy chắc là người quen đây mà ;-) . Cái nick của bồ lạ lạ không biết của ai, lâu lắm rồi Duy không vào BKIT. Mấy khóa sau làm cái BKIT màu mè quá :(

    Comment bởi Duy Do | 30/09/2008 | Trả lời

  10. [...] ByteTokenizer – a Java class util By Me « Duy Do’s WebLog (Java,Utility,Snippet) [...]

    Pingback bởi fritz freiheit.com » Link dump is back | 06/10/2008 | Trả lời

  11. Hôm bữa mới nghĩ ra cái này Duy ui, thiệt ra mình đâu cần thiết kết lại từ đầu ByteTokenizer, với cách implement của Duy chỉ có thể cho 1 delimiter. :(
    Nếu wrap từ bytes thành string dùng StringTokenizer sau đó implement lại các interface trả ra lại thành bytes. Như vậy có thể kế thừa lại khả năng nhiều delimiters và cũng không cần phải implement hết các hành động “nextElement” hoặc “nextToken”. :D
    Ý nghĩ thôi chứ TT chưa có implement, nay mai ứng dụng trong protocol chắc sẽ dùng thử. ;)

    Comment bởi TrangTay | 24/10/2008 | Trả lời

  12. Cách của anh sẽ gây ra vấn đề về performance. Cái ByteTokenizer của em sẽ hiệu quả trong việc xử lý các mảng byte. Hiện tại lớp này chỉ xài được cho một delimeter, không truyền nhiều delimeter như StringTokenizer được. Em đang thêm phần này ;-)

    Comment bởi Duy Do | 25/10/2008 | Trả lời


Để lại phản hồi