Thursday, December 30, 2004

tsunami video

Just thought I share what I found in Google while searching for the Tsunami video..
There is actually a better video which was aired last night on national TV, about a whole family in Aceh (Indonesia) who have to stay in the second level of their house watching nothing but water and ruins of the neighbouring houses surrounding their house.

The numbers are increasing, it's more than 27,000+ dead now, in Indonesia only. Two whole villages are wiped out. Please help in anyway that you can.

Phuket Beach, Thailand

Patong Beach, Thailand

Srilanka Resort


Penang Beach, Malaysia


Koh Lanta, Thailand

- eric

Wednesday, December 29, 2004

Carcassonne Game Session Report 17-Dec-04

A couple Friday ago, I had a chance to gather some of my friends to play some board game at my place. The game we played that night was Carcassonne. If you're not familiar with the game, you can check it out on FunAgainGames or BoardGameGeek. It was a good gaming session though I expect more people would be able to join the session. Anyway, following is the session report:

Carcassonne Session I

That Friday, I was able to ask Thomas, Rudi & Nita to come and play Carcassonne at my place. Even though we're still playing the basic rule (no farmers, due to Thomas & Rudi being a new player to the game), I have introduced new variants to the game.

The first variant was the "Monastery for All" variant created by Bernd Eisenstein. The variant rule is "In the beginning each player is assigned one monastery, which is discarded open. This monastery can be put into play instead of drawing a
new card at any point of time." I believe this variant can reduce the luck factor of the game, due to one player drawing more than one monastery, while another player have none.

The "Monastery for All" variant

The second variant I tried was "Collective Planning of Countryside" variant, also created by Bernd Eisenstein. The variant rule is "3 countryside cards are displayed open. The players can decide, whether to choose an open or a face down countryside card. After the move the open cards are completed to 3." I want to try whether this variant will add more strategic thinking into the game or not.

Anyway, after explaining the new variants to the other players, the game began. Nita started with Green, followed by Thomas in Red, then Rudi with Blue, and finally me in Yellow.

Thomas starts the game

The game started with Thomas (Red) and Nita (Green) working together to build a rather large city. Since we're playing the "Collective Planning of Countryside" variant, it's very clear to see three additional tiles, and this has helped both Thomas and Nita in growing their city bigger very quickly. On the other hand, I kept getting the tile with part of the city only on one side (small city tile), which makes it very hard for me to create a large enough city. So, the only way for me to gain actual points was to make an attempt on joining Thomas & Nita's city.

After a few turns, that attempt succeeded. I finally joined Thomas & Nita's city. To make matter worse for them, I tried to add more knights into the city, to claim majority in the city. If this attempt succeeded, then I'd have all the points of the city to me only, while Thomas & Nita received nothing. While we're fighting on that particular city, Rudi was happily creating his own city, noted by the first completed city was his.

When I finally able to dominate the city, Thomas & Nita immediately claimed the same domination of the city and closed it before anything bad (from me, I guess) happened. All three of us scored 22 points, while Rudi received nothing for that big city.

Interesting notes to the game were the "Monastery for All" variant did help to reduce the luck factor of the game, while on the other hand the "Collective Planning of Countryside" variant only boosted the early game because we had so many great options open for us, but the variant got stucked in the mid-to-late game because all three tiles were all showing straight roads, which meant almost nothing.

Anyway, the game ended with Rudi as the highest scoring player with 67 points, followed by Thomas with 64 points, then Nita with 52 points, and finally me with 50 points. I guess Rudi really wins by himself while we're too busy fighting over a single city.

Rudi, the winner of the first session

Carcassonne Session II

We decided to play one more time, but this time we're trying a new variant to replace the "Collective Planning of Countryside" variant, which Thomas & Nita didn't like. I decided to introduce my own variant. So, instead of having 3 open cards at all time which are available to all players, the new variant allows each player to have another card available only to him as an additional option after picking a close card, prior to playing any card. I believe this will add more strategic thinking & planning to the game without sacrificing many times wasted or the game itself being rather boring in the mid-to-late stage.

In the second game, again, my luck hasn't changed. I still received the small city tile, along with many roads. The only reason I was able to make any real points was because I was able to join 3 cities created by the other players, while scoring 2 cloisters and 17 points from the 3 roads I have.

Nita won the second game with 98 points, then I'm second with 94 points, followed by Thomas with 75 points, and former winner Rudi, with the same points as the first game.. :D

Nita, the winner of the second session

Tuesday, December 28, 2004


South Asia & South East Asia have just been hit by a great tsunami caused by the 5th largest earthquake since 1900, causing 24,000+ people killed, many thousands missing & millions injured. Indonesia is no exception, the death toll is reaching 4,000+ already.

Search Google News for "Tsunami" or "Tsunami Indonesia"..
Please help in any way that you can..

Friday, December 24, 2004

Query by Example or by Criteria

Again, misconception on ORM. A colleague of mine tries to query the database to find a record based on a field of the same table. He tries to use a query by example (a feature in Toplink which allows us to find the matching persistent object(s) if you provide the sample instance). I don't know whether this feature exists in other ORM technologies, e.g. Hibernate, but it's becoming quite handy.

For example, let's say we have an Employee table and a Department table. The Employee has a DepartmentID as his FK field, referring to the PK field of the Department table, with the same name.

To find a Department by its DepartmentID (PK field), all we need to do is as follows:

// initializing the query object
ReadObjectQuery query = new ReadObjectQuery ();

// preparing the example
Department dept = new Department ();
dept.setDepartmentID ("someID");

// set the example
query.setExampleObject (dept);

// execute the query on the current DB session
dept = (Department) session.executeQuery (query);

It's quite simple and straightforward (as it should be). We have retrieve a full Department object by performing such query. Depending on our indirection settings, the Department object may or may not have resolved all of its foreign key fields.

Now, moving on the next step. Let's try to find all Employees which are working on that Department (as specified by the DepartmentID). An obvious code may look something like my colleague did:

// initializing the query object
ReadAllQuery q = new ReadAllQuery ();

// preparing the example
Employee emp = new Employee ();
emp.setDepartment (dept);

// set the example
q.setExampleObject (emp);

// execute the query on the current DB session
Vector employees = (Vector) session.executeQuery (query);

At first, there's nothing wrong with the code. Of course, the results are showing correctly. Then, what's the problem? The only problem with this approach is when the Department object is actually a big table consisting of many fields, and many foreign key fields. In the background process, Toplink tries to generate a SELECT SQL Statement for the query by joining all of the foreign tables, and by putting numerous WHERE clauses on all the Department fields.

Why did this occur? It's all because we're using the wrong approach here. Query by example will read all non-null values from the example object, and create the same number (if not more) WHERE clauses as the number of non-null values in the example object. The query by example approach may be a convenient and suitable approach for query which involves the PK field of the table, or for query which involves a small number of known field values of the table. But, for a different case as shown above, a different approach needs to be chosen.

My colleague performed the query by example on his table (which more complex than the example above), and I have to wait 30 seconds to see the query results. I changed the code to use query by criteria instead, and it only cost me 3 seconds to wait.. :D

I think this could be a good example of working on/using something you don't know very well.. :D

Further Reading:
Hibernate in Action

Monday, December 13, 2004

POI for Excel parser

It's been over a month since my last post. Sorry guys. It's been busy at work, and no internet connection. I had a nice two weeks vacation though, and it was fun.. :D

Anyway, during the vacation, I happened to help a friend of mine finish up his project. I helped him to create a component to parse resume documents. These documents can be in Word, Excel, HTML or plain text. He wanted me to be able to parse the content of the documents, removing all the formatting code, and then index the document and store it into the database for google-like search later on.

Again, Java and the Open Source community has been a great help in achieving this goal. There are many options for parsing documents, but here are my final choices:
- Apache Jakarta POI, for parsing Excel documents
- TextMining, for parsing Word documents
- HTMLParser, for parsing HTML documents
These open source libraries have helped me in a great deal to complete the component.

Here's a sample code for parsing an Excel document:

public interface Parser {
  public void setFileName (String fileName);
  public String getParsedText ();
  public void parse ();

public class ExcelParser implements Parser {
  private String fileName;
  private String parsedText;

   * Using HSSF in POI library, this method will parse
   * the Excel file, and extract all the values for
   * every sheet, and combine them into a single
   * String.
  public void parse () {
    // initializing the InputStream from a file using
    // POIFSFileSystem, before converting the result
    // into an HSSFWorkbook instance
    HSSFWorkbook wb = null;
    try {
      InputStream is = this.getClass()
        .getResourceAsStream (fileName);
      POIFSFileSystem fs = new POIFSFileSystem (is);
      wb = new HSSFWorkbook (fs);
    } catch (IOException e) {

    // start appending the values
    StringBuffer sb = new StringBuffer ();

    // loop for every worksheet in the workbook
    int numOfSheets = wb.getNumberOfSheets();
    for (int i=0; i<numofsheets; i++) {
      HSSFSheet sheet = wb.getSheetAt (i);

      // loop for every row in each worksheet
      for (Iterator rows = sheet.rowIterator();
              rows.hasNext(); ) {
        HSSFRow row = (HSSFRow) ();
        short c1 = row.getFirstCellNum();
        short c2 = row.getLastCellNum();

        // loop for every cell in each row
        for (short c=c1; c<c2; c++) {
          HSSFCell cell = row.getCell(c);
          if (cell != null) {
            String cellValue = getCellValue (cell);
            if (cellValue != null &&
                cellValue.trim().length() > 0) {

              // append the value of the cell separated
              // by a SPACE
              b.append (TextFilter.SPACE)
               .append (cellValue);

    // store the parsed Text
    parsedText = sb.toString().trim();

    // filter the parsed Text, replacing the forbidden
    // characters with SPACE
    parsedText = TextFilter.filterForbiddenCharacters (

   * This is a helper method to retrieve the value of a
   * cell regardles of its type, which will be converted
   * into a String.
   * @param cell
   * @return
  private String getCellValue (HSSFCell cell) {
    if (cell == null) return null;

    String result = null;

    int cellType = cell.getCellType();
    switch (cellType) {
      case HSSFCell.CELL_TYPE_BLANK:
        result = "";
        result = cell.getBooleanCellValue() ?
          "true" : "false";
      case HSSFCell.CELL_TYPE_ERROR:
        result = "ERROR: " + cell.getErrorCellValue();
        result = cell.getCellFormula();
        HSSFCellStyle cellStyle = cell.getCellStyle();
        short dataFormat = cellStyle.getDataFormat();

        // assumption is made that dataFormat = 15,
        // when cellType is HSSFCell.CELL_TYPE_NUMERIC
        // is equal to a DATE format.
        if (dataFormat == 15) {
          result = cell.getDateCellValue().toString();
        } else {
          result = String.valueOf (

        result = cell.getStringCellValue();
      default: break;

    return result;

  public void setFileName(String fileName) {
    this.fileName = fileName;

  public String getParsedText() {
    return parsedText;

public class TextFilter {
  public static final char CH07 = (char) 7;
  public static final char CH10 = (char) 10;
  public static final char CH13 = (char) 13;
  public static final char[] DEFAULT_FORBIDDEN_CHARACTERS =
    { CH07, CH10, CH13 };
  public static final char SPACE = (char) 32;

  public static String filterForbiddenCharacters (
    String s, char[] forbidden) {
    if (s == null || s.trim().length() <= 0) return s;
      for (int i = 0; i<forbidden.length; i++) {
        char c = forbidden[i];
        s = s.replace (c, SPACE);
      return s;

I'll try sharing more codes later.. :D