2004-08-19

Layout Change

I am making some changes in the template just now. Also, I am going to start using the commenting facility of Blogger.com. There may some problems for a while.

Using Reflection for Actions

There is no doubt that inner classes are an important aspect of the Java programming language. The easiest way to define an action in a GUI application is to subclass the javax.swing.AbstractAction class and override its actionPerformed method, usually in an inner class. Yet, actions are so frequent in these applications that it would be nice to simplify the process further. Here, I suggest a way that I have found very useful.

If we have an action named "Open", we might expect it to call a method like "onOpenExecute" without creating a specific class for it. This can be done, using the Java Reflection API.

The ReflectiveAction class, whose code is presented below, uses the reflection API to find its relevant execute method in the owner object. The method's name is constructed using the "key" property of the ReflectiveAction instance. For example, if a ReflectiveAction has a key value of "open", it will reflect on the owner object's class, to see if it contains a public method called "onOpenExecute". If this method is found, it will be called upon firing of this action. For a key value of "file.print", the execute method must be named onFilePrintExecute. Period character is ignored, so a key value of "Choose..." needs a method called onChooseExecute. Other special characters should not be used in the key field (I have done my best to keep the code as simple as possible).

The source code for the ReflectiveAction class is presented below. It must be taken in mind that in this example, an enhanced for loop has been used, which is a feature recently added to Java since JDK 1.5. When compiling this file with javac, a -source 1.5 parameter must be supplied to show that the source is compliant with JDK 1.5 and not the earlier versions. Interestingly, when I used the -target 1.5 parameter in addition to that, the reflection stuff kept hanging the JVM when running. I don't know if this bug is still there or has been corrected in later SDKs (I have build 1.5.0-beta-b31), but anyway, without this option, everything goes well, and I am not in the mood of keeping track of bugs!

Listing 1. Source code of ReflectiveAction.java
/* 
 * ReflectiveAction.java 
 * (c) Ghasem Kiani 
 * 18/08/2004 06:33:49 PM 
 * ghasemkiani@yahoo.com 
 */ 
 
package com.ghasemkiani.temp; 
 
import java.awt.*; 
import java.awt.event.*; 
import javax.swing.*; 
import java.lang.reflect.Method; 
 
public class ReflectiveAction extends AbstractAction 
{ 
  private Method executeMethod; 
  private String key; 
  public void setKey(String key) 
  { 
    this.key = key; 
  } 
  public String getKey() 
  { 
    if(key == null) key = new String(); 
    return key; 
  } 
  private String getKeyAsTitleCase() 
  { 
    String[] parts = getKey().split("\\."); 
    String result = ""; 
    for(String part: parts) 
    { 
      if(part != null) 
      { 
        if(part.length() > 0) 
          result += part.substring(0, 1).toUpperCase(); 
        if(part.length() > 1) 
          result += part.substring(1); 
      } 
    } 
    return result; 
  } 
  private Object owner; 
  public void setOwner(Object owner) 
  { 
    this.owner = owner; 
    if(owner != null) 
    { 
      Class oc = owner.getClass(); 
      String em = "on" + getKeyAsTitleCase() + "Execute"; 
      try 
      { 
        executeMethod = oc.getMethod(em, new Class[0]); 
      } 
      catch(Exception e) 
      { 
        executeMethod = null; 
      } 
    } 
    else 
    { 
      executeMethod = null; 
    } 
  } 
  public Object getOwner() 
  { 
    return owner; 
  } 
  public ReflectiveAction() 
  { 
    super(); 
  } 
  public ReflectiveAction(Object owner, String key) 
  { 
    super(key); 
    setKey(key); 
    setOwner(owner); 
  } 
  public ReflectiveAction(Object owner, String key, String name) 
  { 
    super(name); 
    setKey(key); 
    setOwner(owner); 
  } 
  public ReflectiveAction(Object owner, String key, Icon icon) 
  { 
    super(key, icon); 
    setKey(key); 
    setOwner(owner); 
  } 
  public ReflectiveAction(Object owner, String key, 
    String name, Icon icon) 
  { 
    super(name, icon); 
    setKey(key); 
    setOwner(owner); 
  } 
  public void actionPerformed(ActionEvent ae) 
  { 
    try 
    { 
      if(executeMethod != null) 
        executeMethod.invoke(owner, new Object[0]); 
    } 
    catch(Exception e) 
    { 
      e.printStackTrace(); 
    } 
  } 
} 

Here is a sample application using the ReflectiveAction class. I think that the code is self-descriptive. Also, be warned that I hate comments in my code. As unacceptable as it may be, I use them very rarely.

Listing 2. Source code of TestApplication.java
/* 
 * TestApplication.java 
 * (c) Ghasem Kiani 
 * 18/08/2004 06:58:34 PM 
 * ghasemkiani@yahoo.com 
 */ 
 
package com.ghasemkiani.temp; 
 
import java.awt.*; 
import javax.swing.*; 
import com.ghasemkiani.temp.ReflectiveAction; 
 
public class TestApplication extends JFrame 
{ 
  JPanel jp; 
  public TestApplication() 
  { 
    super("Test Application"); 
    setDefaultCloseOperation(DISPOSE_ON_CLOSE); 
    JToolBar jtb = new JToolBar(); 
    getContentPane().add(jtb, BorderLayout.NORTH); 
    jp = new JPanel(); 
    getContentPane().add(jp); 
    jtb.add(new ReflectiveAction(this, "Green")); 
    jtb.add(new ReflectiveAction(this, "Blue")); 
    jtb.add(new ReflectiveAction(this, "Red")); 
    jtb.add(new ReflectiveAction(this, "Choose...")); 
    jtb.add(new ReflectiveAction(this, "Dummy")); 
    jtb.add(new ReflectiveAction(this, "Exit")); 
    setBounds(new Rectangle(100, 100, 400, 240)); 
    setVisible(true); 
  } 
  public void onGreenExecute() 
  { 
    JOptionPane.showMessageDialog(this, 
      "I don't actually become green!"); 
  } 
  public void onBlueExecute() 
  { 
    JOptionPane.showMessageDialog(this, 
      "I don't actually become blue!"); 
  } 
  public void onRedExecute() 
  { 
    jp.setBackground(Color.red); 
  } 
  public void onChooseExecute() 
  { 
    Color c = JColorChooser.showDialog(this, 
      "Choose a color", jp.getBackground()); 
    if(c != null) jp.setBackground(c); 
  } 
  public void onExitExecute() 
  { 
    dispose(); 
  } 
  public static void main(String[] args) 
  { 
    JFrame.setDefaultLookAndFeelDecorated(true); 
    JDialog.setDefaultLookAndFeelDecorated(true); 
    new TestApplication(); 
  } 
} 

Since I am willing to see the result of a code when I read an example code in an article, I present here a few screenshots of the TestApplication class running.

Figure 1 This is a picture of the application window.

This figure shows that the reflection has actually taken place, and the right method hase been called after clicking the Red and Green button.

Figure 2 This is what happens after clicking the Green button.

Another screenshot, just to make it a little saltier! (I don't know if you can take the Persian nuances of words.)

Figure 3 This is what has happened after clicking the Choose... button.

Some may understandably argue that this is againtst the spirit of the Java programming language, having a strong object-oriented orientation. But, it is an equally important argument that such an attitude is the rule in languages like Delphi and lisp (from a totally different point of view relative to Delphi -- here I mean lexical closures and metaprogramming, if I use the right terms).

The source and compiled classes of this example can be downloaded from here.

2004-08-06

Encoding in JavaServer Pages

Encoding is an important issue in software i18n. Iranian developers have spent enormous energy in tackling encoding problems, ever since computer programming entered to this country. Fortunately, Java has a marvellous support for various encodings, including Unicode and its variants. Regarding the encoding issue in JSP applications, these things should be taken in mind:

Encoding of the JSP Page Itself

The best solution for this problem is to write JSP documents, i.e., JSP pages that are standard XML documents. The XML standard has an efficient mechanism for determining the document encoding:

<?xml version="1.0" encoding="windows-1256" ?>
 <jsp:root version="2.0"
  xmlns:jsp="http://java.sun.com/JSP/Page">
  <!-- Page content... --></jsp:root>

Encoding of the Response

By setting the content type in the following manner, we can declare the encoding of the response that is sent to the client:

<?xml version="1.0" encoding="windows-1256" ?>
 <jsp:root version="2.0"
  xmlns:jsp="http://java.sun.com/JSP/Page">
  <jsp:directive.page contentType="text/html; charset=UTF-8" />
  <!-- Page content... --></jsp:root>

But there is a problem if you use the fmt:setLocale tag to set the locale -- the tag handler will automatically assign a (presumably) appropriate encoding for the response. This is by no means apprpriate, since we have no locale for the Persian language. On the other hand, the current implementation of Apache JSTL project assigns the ISO-8859-6 encoding to Arabic locales (like ar_SA). This encoding is not sufficient for Persian language content. The following seems to solve the problem satisfatorily:

<?xml version="1.0" encoding="windows-1256" ?>
 <jsp:root version="2.0"
  xmlns:jsp="http://java.sun.com/JSP/Page"
  xmlns:c="http://java.sun.com/jsp/jstl/core"
  xmlns:fmt="http://java.sun.com/jsp/jstl/fmt">
  <c:set target="${pageContext.response}" property="characterEncoding" value="UTF-8" />
  <jsp:directive.page contentType="text/html" />
  <fmt:setLocale value="ar-SA" />
  <!-- Page content... --></jsp:root>

Encoding of the Request

Setting the character encoding of the HTTP request is essential for proper interpretation of form data. This can most easily be done by using a JSTL tag:

<?xml version="1.0" encoding="windows-1256" ?>
 <jsp:root version="2.0"
  xmlns:jsp="http://java.sun.com/JSP/Page"
  xmlns:c="http://java.sun.com/jsp/jstl/core"
  xmlns:fmt="http://java.sun.com/jsp/jstl/fmt">
  <c:set target="${pageContext.response}" property="characterEncoding" value="UTF-8" />
  <jsp:directive.page contentType="text/html" />
  <fmt:setLocale value="ar-SA" />
  <fmt:requestEncoding value="UTF-8" />
  <!-- Page content... --></jsp:root>

Encoding of Database Connections

If you are using pure java JDBC relational database management systems and drivers, such as IDB, HSQLDB, and PostgreSQL, you will not have any problems regarding the character encoding of the database connection. But if you are using the sun.jdbc.odbc.JdbcOdbcDriver driver, there may be some problems.

However, in recent versions of the JDBC-ODBC bridge, there is a way to set the connection charset:

<?xml version="1.0" encoding="windows-1256" ?>
 <jsp:root version="2.0"
  xmlns:jsp="http://java.sun.com/JSP/Page"
  xmlns:c="http://java.sun.com/jsp/jstl/core"
  xmlns:fmt="http://java.sun.com/jsp/jstl/fmt">
  <c:set target="${pageContext.response}" property="characterEncoding" value="UTF-8" />
  <jsp:directive.page contentType="text/html" />
  <fmt:setLocale value="ar-SA" />
  <fmt:requestEncoding value="UTF-8" />
  <jsp:scriptlet><![CDATA[
    Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
    Properties p = new Properties();
    p.put("charSet", "windows-1256");
    p.put("user", "ghasem");
    p.put("password", "100");
    Connection c = DriverManager.getConnection("jdbc:odbc:MyTestDb", p);
    // ...
  ]]></jsp:scriptlet>
  <!-- Page content... --></jsp:root>

Of course, it would be much better to use this DataSource instead:

<?xml version="1.0" encoding="windows-1256" ?>
 <jsp:root version="2.0"
  xmlns:jsp="http://java.sun.com/JSP/Page"
  xmlns:c="http://java.sun.com/jsp/jstl/core"
  xmlns:fmt="http://java.sun.com/jsp/jstl/fmt"
  xmlns:sql="http://java.sun.com/jsp/jstl/sql">
  <c:set target="${pageContext.response}" property="characterEncoding" value="UTF-8" />
  <jsp:directive.page contentType="text/html" />
  <fmt:setLocale value="ar-SA" />
  <fmt:requestEncoding value="UTF-8" />
  <jsp:useBean scope="page" id="ds" class="sun.jdbc.odbc.ee.DataSource" />
  <c:set target="${ds}" property="databaseName" value="MyTestDb" />
  <c:set target="${ds}" property="user" value="ghasem" />
  <c:set target="${ds}" property="password" value="11" />
  <c:set target="${ds}" property="charSet" value="windows-1256" />
  <sql:setDataSource scope="page" var="source" dataSource="${ds}" />
  <sql:query scope="page" var="res" dataSource="${source}"><![CDATA[
    SELECT author, title, url FROM Books WHERE author like '%مطهري%'
  ]]></sql:query>
  <!-- Page content... --></jsp:root>

Note:

The following software have been used for creating and testing these code snippets (earlier versions may have some discrepancies):

  1. Java 2 Standard Edition, Software development Kit, version 1.5.0 beta 31
  2. Tomcat, version 5.0.6
  3. Servlet 2.4 and JSP 2.0 (these are distributed together with the above-mentioned Tomcat release)
  4. JSTL version 1.1

Surely, much more can be said about JSP. These code fragments are only guides to show the ways we can set encoding in JSP applications. Each of the technologies mentioned above (servlets, JSP, JDBC, XML, JSTL, etc.) warrants a much deeper explanation. I may write some more notes about these technologies in the future.

رمزگذاري در صفحات سرور جاوا

رمزگذاري (encoding) يكي از مهم‌ترين مسايل در بين‌المللي‌سازي برنامه‌ها است, و در طول ساليان اخير, مقدار زيادي از تلاش برنامه‌نويسان ايراني را به خود مشغول كرده است. خوشبختانه, جاوا بهترين پشتيباني را براي ارائه‌ي برنامه‌ها با رمزگذاري‌هاي معتبر (مثلاً يونيكد) دارد. در مورد رمزگذاري صفحات سرور جاوا چند نكته حايز اهميت است:

رمزگذاري خود صفحه‌ي JSP

بهتر است از سند JSP استفاده كنيم, يعني صفحاتي كه سند XML هستند. در اين صورت, رمزگذاري در ابتداي پرونده مشخص مي‌شود:

<?xml version="1.0" encoding="windows-1256" ?>
 <jsp:root version="2.0"
  xmlns:jsp="http://java.sun.com/JSP/Page">
  <!-- متن صفحه... --></jsp:root>

رمزگذاري پاسخ

براي تعيين رمزگذاري صفحه‌اي كه به دست مشتري مي‌رسد, نوع محتوا را تعيين مي‌كنيم:

<?xml version="1.0" encoding="windows-1256" ?>
 <jsp:root version="2.0"
  xmlns:jsp="http://java.sun.com/JSP/Page">
  <jsp:directive.page contentType="text/html; charset=UTF-8" />
  <!-- متن صفحه... --></jsp:root>

مسئله اين است كه اگر از برگه‌ي (يعني tag) تعيين محل fmt:setLocale استفاده كنيد, خودبخود رمزگذاري پاسخ را مطابق محل (locale) داده شده تعيين خواهد كرد. از آنجا كه براي فارسي در جاوا محلي تعريف نشده است, و ممكن است خيلي سيستم‌ها محل پيش‌فرض را عربي (مثلاً ar_SA) تعريف كرده باشند, در صورت تعيين اين محل, رمزگذاري صفحه در پياده‌سازي JSTL كنوني Apache رمزگذاري ISO-8859-6 خواهد بود كه براي فارسي اصلاً مناسب نيست. اگر به صورت زير عمل كنيم, ظاهراً اين مسئله حل مي‌شود:

<?xml version="1.0" encoding="windows-1256" ?>
 <jsp:root version="2.0"
  xmlns:jsp="http://java.sun.com/JSP/Page"
  xmlns:c="http://java.sun.com/jsp/jstl/core"
  xmlns:fmt="http://java.sun.com/jsp/jstl/fmt">
  <c:set target="${pageContext.response}" property="characterEncoding" value="UTF-8" />
  <jsp:directive.page contentType="text/html" />
  <fmt:setLocale value="ar-SA" />
  <!-- متن صفحه... --></jsp:root>

رمزگذاري تقاضا

تعيين اين رمزگذاري در گرفتن داده‌هاي فرم اهميت زيادي دارد. در اين مورد, بهترين كار استفاده از JSTL است:

<?xml version="1.0" encoding="windows-1256" ?>
 <jsp:root version="2.0"
  xmlns:jsp="http://java.sun.com/JSP/Page"
  xmlns:c="http://java.sun.com/jsp/jstl/core"
  xmlns:fmt="http://java.sun.com/jsp/jstl/fmt">
  <c:set target="${pageContext.response}" property="characterEncoding" value="UTF-8" />
  <jsp:directive.page contentType="text/html" />
  <fmt:setLocale value="ar-SA" />
  <fmt:requestEncoding value="UTF-8" />
  <!-- متن صفحه... --></jsp:root>

رمزگذاري پايگاه داده‌اي

در گردانه‌هاي JDBC جاواي خالص كه خود پايگاه داده‌اي تشكيل مي‌دهند (خود سرور پايگاه داده‌اي هستند), مانند IDB و HSQLDB و PostgreSQL معمولاً مشكلي خاصي وجود ندارد. ولي وقتي از گردانه‌ي sun.jdbc.odbc.JdbcOdbcDriver استفاده مي‌كنيم, ممكن است دچار مشكل بشويم.

با اين حال, در نسخه‌هاي جديد پل JDBC و ODBC, امكان تعيين رمزگذاري ارتباط به صورت زير وجود دارد:

<?xml version="1.0" encoding="windows-1256" ?>
 <jsp:root version="2.0"
  xmlns:jsp="http://java.sun.com/JSP/Page"
  xmlns:c="http://java.sun.com/jsp/jstl/core"
  xmlns:fmt="http://java.sun.com/jsp/jstl/fmt">
  <c:set target="${pageContext.response}" property="characterEncoding" value="UTF-8" />
  <jsp:directive.page contentType="text/html" />
  <fmt:setLocale value="ar-SA" />
  <fmt:requestEncoding value="UTF-8" />
  <jsp:scriptlet><![CDATA[
    Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
    Properties p = new Properties();
    p.put("charSet", "windows-1256");
    p.put("user", "ghasem");
    p.put("password", "100");
    Connection c = DriverManager.getConnection("jdbc:odbc:MyTestDb", p);
    // ...
  ]]></jsp:scriptlet>
  <!-- متن صفحه... --></jsp:root>

البته, راه بهتر آن است كه از اين DataSource استفاده شود:

<?xml version="1.0" encoding="windows-1256" ?>
 <jsp:root version="2.0"
  xmlns:jsp="http://java.sun.com/JSP/Page"
  xmlns:c="http://java.sun.com/jsp/jstl/core"
  xmlns:fmt="http://java.sun.com/jsp/jstl/fmt"
  xmlns:sql="http://java.sun.com/jsp/jstl/sql">
  <c:set target="${pageContext.response}" property="characterEncoding" value="UTF-8" />
  <jsp:directive.page contentType="text/html" />
  <fmt:setLocale value="ar-SA" />
  <fmt:requestEncoding value="UTF-8" />
  <jsp:useBean scope="page" id="ds" class="sun.jdbc.odbc.ee.DataSource" />
  <c:set target="${ds}" property="databaseName" value="MyTestDb" />
  <c:set target="${ds}" property="user" value="ghasem" />
  <c:set target="${ds}" property="password" value="11" />
  <c:set target="${ds}" property="charSet" value="windows-1256" />
  <sql:setDataSource scope="page" var="source" dataSource="${ds}" />
  <sql:query scope="page" var="res" dataSource="${source}"><![CDATA[
    SELECT author, title, url FROM Books WHERE author like '%مطهري%'
  ]]></sql:query>
  <!-- متن صفحه... --></jsp:root>

توجه:

براي تهيه‌ي اين متن‌هاي JSP و آزمايش آنها از نرم‌افزارهاي زير استفاده شده است (نسخه‌هاي پايين‌تر ممكن است تفاوت‌هايي داشته باشند):

  1. كيت برنامه‌نويسي جاواي 1.5.0
  2. تامكت, نسخه‌ي 5.0.6
  3. سرولت نسخه‌ي 2.4 و JSP نسخه‌ي 2.0 (در توزيع تامكت فوق موجود است)
  4. JSTL نسخه‌ي 1.1

روشن است كه در باره‌ي صفحات سرور جاوا حرف‌هاي زيادي براي گفتن وجود دارد. متن‌هاي ارائه شده در اينجا صرفاً براي نشان دادن راهكارها و قابليت‌هاي تعيين رمزگذاري بود. در باره‌ي هر كدام از مباحث فوق (سرولت, JSP, كتابخانه‌هاي برگه [مانند JSTL], ارتباط با پايگاه‌هاي داده‌اي [فناوري JDBC], و غيره) مطالب زيادي مي‌توان گفت, كه شايد بعداً به برخي از آنها بپردازم.