Aspect Oriented Programming and Internationalization Blog

Version 2


    Aspect-oriented programming (AOP), although specialized in nature, can facilitate a range of useful separations of concerns. One such area is internationalization (often called "I18N" because of the 18 characters between the first and last letter of the word). I18N is big business in a global software marketplace. In this article I'll use AOP to show how part of the I18N problem can be solved with the use of aspects in an additive, not-too-invasive manner.

    The case for low-touch programming

    I use I18N as an example here, but a deeper requirement motivates my selection of this topic. Technology customers now demand three elements in their software and system procurements:

    • Low cost
    • Flexible solutions
    • Commodity technology

    Examples of systems that fulfill these three needs include the various flavors of Linux and the many open-source products and toolkits. When Sun open-sources Java, as promised, it's likely the open-source development effort will accelerate. This may, in turn, increase the consumer appetite for complex products with the above three elements. What does this growth mean to programmers?

    As engineers, we must strive to meet these three needs, and one useful approach is to use what I call low-touch programming (LTP). With LTP, which is just good design practice, you add or modify code in a manner that's as non-invasive as possible. Ideally, LTP should allow you to drop working classes into an operational product. The dropped-in classes then provide any required extra capabilities and help fulfill at least the first two requirements listed earlier. AOP is an unusual technology that can facilitate such class insertion and other LTP needs.

    All the code in this article is available for download in the Resources section. If you want to run the AOP code, you'll need to get a copy of AspectJ.

    To begin with, I define a few simple I18N requirements. I then launch into some Java examples to fulfill these requirements, I finish with the AspectJ code.

    Some I18N Requirements

    At its core, I18N is mostly a problem of separating code from text resources. This is a bit like the now-routine separation of business logic (increasingly referred to as service logic) from presentation. In I18N, you want to be able to easily change language and data formats—if possible, without having to recompile—for entities such as:

    • Dialog text blocks
    • Help files
    • GUI element text tags (for example, button labels)
    • Currency signs
    • Delimiters for currency amounts
    • Dates

    On a more general level, these requirements describe text substitution problems.

    In the pre-Java days, I18N constituted a rather arduous process of manually extracting I18N elements and placing them in disk files. This extraction process often occurred as the software began to be used in foreign language locations. The consequence was that some poor soul had to go through the code, pulling out text strings and copying them into files. The files were then sent for translation, after which some cunning scheme had to be devised for locating, loading, and reading the text strings back into the program.

    At runtime, the I18N elements would be dynamically inserted by the code to produce an internationalized version. The problem with these schemes was their cumbersome and proprietary nature. Fortunately, I18N began to be considered a language issue, and Java includes standard support features. So you can internationalize Java code from the beginning with little extra cost. I must say I have worked on products that were destined only for the U.S. market and therefore were not internationalized. However, my inclination would always be to I18N-enable code because it's good practice and also because it's pretty easy.

    To help make things concrete, let's look at some I18N examples before looking at the AOP area.

    Apache Axis and I18N

    You can use Java to access some powerful I18N techniques. An example of I18N use occurs with Apache Axis, the open-source SOAP server and client. Axis includes I18N support as a standard feature. If I want to add new text strings to Axis, I simply modify the source code and add an entry to a resource.propertiesfile. The properties file contains translation and usage instructions with entries of the form<key>=<message>. Here is an example:

    textstring00=Hello, my name is {0}, my job title is {1}

    The element textstring00 is the key that the code uses to access this message. The text after the equals sign is the message text, and the {number} syntax defines the location for inserts in the code.

    Next, I use the static method org.apache.axis.I18n.Messages.getMessage to obtain the text, and I add code inserts as required:

    Messages.getMessage("textstring00", "John", "programmer");

    The net effect is a string that looks something like:

    Hello, my name is John my job title is programmer

    Using this mechanism, Axis has been I18N-enabled from the beginning of the project. This may turn out to be a pretty smart move on the part of the contributors because it could open up the project to a global user base.

    I did say it's pretty easy to achieve this level of I18N capability. You can see that with a little planning, the text string numbering scheme described above (that is,textstring00) could also be used to reserve blocks of numbers for different parts of your code or even for different software products. So, what about GUIs?

    GUI Elements and I18N

    Perhaps one of the most impressive Java I18N features is that it allows you to change GUI element labels without recompiling. To illustrate this capability, I've cobbled together a little one-button Swing program called The program can be invoked for either French or U.S. locales. To run in French mode, you simply type the following from a DOS console:

    java I18NSwingDemo fr FR

    The two parameters represent language (French) and region (France), respectively. The program invocation produces the GUI displayed in Figure 1.

    An I18N-enabled Swing program running in a French locale
    Figure 1. An I18N-enabled Swing program running in a French locale

    To run the program in U.S. mode, you type the simple command:

    java I18NSwingDemo en US

    This produces the GUI displayed in Figure 2.

    An I18N-enabled Swing program running in a U.S. locale
    Figure 2. An I18N-enabled Swing program running in a U.S. locale

    Achieving this is very straightforward. The to-do list is as follows:

    • Create a property file
    • Create a second property file
    • Edit and insert the pidgin French line: exitMessage=Cliquez pour terminer.
    • Edit and insert the line:exitMessage=Click to exit.
    • Save the two property files.
    • Compile the Java class:
    • Run the program with the command-line parameters described above.

    The code is illustrated here in excerpt form:

    language = new String(args[0]); country = new String(args[1]); JFrame firstWindow = new JFrame(); firstWindow.setSize(WIDTH, HEIGHT); firstWindow.setDefaultCloseOperation( JFrame.DO_NOTHING_ON_CLOSE); Locale locale = new Locale(language, country); ResourceBundle captions = ResourceBundle.getBundle("Messages", locale); element1Caption = captions.getString("exitMessage"); JButton endButton = new JButton(element1Caption); EndingListener buttonEar = new EndingListener(); endButton.addActionListener(buttonEar); firstWindow.add(endButton); firstWindow.setVisible(true);

    You can see how the JButton label is read in from the ResourceBundle in a locale-specific fashion.

    It's easy to see how this scheme can be extended to I18N-enable a large application. Clearly, Java helps you solve the static text substitution problem. But what about dynamic text substitution and formatting of dates and currencies (as examples)? In many cases, these data items are calculated at runtime, meaning you can't read them in from disk. To I18N-enable such data entities, you have to modify the source code with all the attendant risks of changing legacy code. The example described here is pretty trivial, but it's not hard to think of examples where date and currency calculations could become complex. To take one case, you may want to code an I18N-enabled projection algorithm for the future value of a multi-currency investment.

    As you'll see in the next section, AOP can be employed to solve this part of the I18N problem and to ameliorate the risk level.

    I18N Using Aspects

    I'll start with a simple e-commerce application that allows you to create an order instance, to select items for purchase, and then to sub-total the order. The following listing illustrates the program output. In the fourth line, the order is completed and sent for billing, at which point the total cost is calculated. Once billing is complete, some simple reporting is done on the order by displaying its financial value:

    >java OrderManagement Created an instance of StockItem Created another instance of StockItem Now routing the order to billing Now in the after code Date of order origination Fri Jul 07 11:55:49 BST 2006 Calculated order value 670.576 Order is complete, ready for reporting Retrieved order value 670.576 Order has a value of 670.576

    The amounts in the listing are numbers with no currency symbols or decimal points. Sometimes, fairly basic accounting applications use this approach and don't add currency details. However, in this case I want to be able to display the current dollar amount total for my order.

    In keeping with my LTP requirements, I want to do this in a fashion that's as lightweight and non-invasive as possible. AOP allows for coding this either as part of an aspect or as a completely new aspect. The aspect code can then be dropped into the operational code as described in the introduction. Clearly, aspects aren't to everyone's taste; I must admit the purist in me does rebel a little at their lack of linearity. But similar arguments were made against code overlay tools back in the RAM-constrained nineties. The key point here is that aspects provide a degree of programmer agility.

    I have my problem requirement and my sketch of the solution. I'll now show you how it's done.

    The Program Structure

    The program consists of four classes:


    To build the program, you can use the fileOrderManagement.lst (an ApsectJ artifact that simply lists the files for compilation) and run the following commands (for readers not familiar with AspectJ):

    ajc -argfile OrderManagement.lst set classpath=.;%classpath%;Charging.class;Order.class;\ OrderManagement.class;StockItem.class;

    I implemented the charging code as an aspect to illustrate how significant production-time functionality can be added with aspects. The Charging aspect is also where I do the I18N coding. To make the code a little easier to follow, I've broken the aspect code into two blocks. The first block defines the pointcut:

    public aspect Charging { pointcut startCharging(Order order): target(order) && call(void Order.completeOrder()); after(Order order): startCharging(order) { System.out.println("Now in the after code"); calculateTotalCharge(order); }

    This code defines the Charging aspect and the conditions under which it executes. The pointcut exists both to pick out execution join points and expose context data. My pointcut is called StartCharging and it operates against instances of the Order class. The pointcut runs when its signature is matched, that is, when thecompleteOrder() method is called.

    Once the pointcut executes, the after code is called, at which point the methodcalculateTotalCharge() is invoked. The latter executes the I18N code:

    public double Order.totalCharge = 0; public double calculateTotalCharge(Order order) { System.out.println("Date of order origination " + order.dateOfOrderCreation); DateFormat df = DateFormat.getDateInstance(); try { System.out.println(df.format(order.dateOfOrderCreation)); } catch(Exception e) { System.out.println("Exception occurred: " + e.getMessage()); System.exit(-1); } Enumeration e = order.orderItems.elements(); while (e.hasMoreElements()) { StockItem aStockItem = (StockItem)e.nextElement(); order.totalCharge += aStockItem.itemFinancialValue; } System.out.println("Calculated order value " + order.totalCharge); return order.totalCharge; } public double getTotalCharge(Order order) { NumberFormat moneyFormatter2 = NumberFormat.getCurrencyInstance(Locale.US); System.out.println("Retrieved order value " + moneyFormatter2.format(order.totalCharge)); return order.totalCharge; } }

    The key elements of the aspect are: the data member calledOrder.totalCharge, and the two methods calledcalculateTotalCharge() andgetTotalCharge() respectively. The first method is used to calculate the total value of the items in the order, and the second method allows for this summed value to be retrieved. As you saw in the earlier listing, the value of the order is670.576. To add a dollar symbol, the following changes are required to the class.

    First, add two new imports:

    import java.text.NumberFormat; import java.util.Locale;

    Then make the following change togetTotalCharge():

    public long getTotalCharge(Order order) { NumberFormat moneyFormatter2 = NumberFormat.getCurrencyInstance(Locale.US); System.out.println("Retrieved order value " + moneyFormatter2.format(order.totalCharge)); return order.totalCharge; }

    When you build and run the program, the modified output is:

    Created an instance of StockItem Created another instance of StockItem Now routing the order to billing Now in the after code Date of order origination Fri Jul 07 12:07:49 BST 2006 Jul 7, 2006 Calculated order value 670.576 Order is complete, ready for reporting Retrieved order value $670.58 Order has a value of 670.576

    Of course, production code most likely would read locale-specific data from some form of persistent storage. The important point here is the use of aspects helps achieve LTP.

    You can see the dollar symbol in the second-to-last line. Notice also that the figure now has only two decimal places and has been rounded up to $670.58. This is pretty much standard practice except with some specialized applications, such as stock price reporting software, where more precision is required. The last line in the listing indicates that the unrounded value has been retained, that is, the display on the second-to-last line is for informational purposes only.

    Date Modification

    You've seen currency formatting. What about dates? Formatting these is also quite straightforward. In the previous program output listing, I displayed the date using the classDateFormat. As it happens, this class helps you to format and parse dates for any locale. For example, to format a date for a different locale, you specify the required locale in the call to getDateInstance():

    DateFormat df = DateFormat.getDateInstance( DateFormat.LONG, Locale.FRANCE); System.out.println(df.format(order.dateOfOrderCreation));

    Inserting the above code into Charging aspect produces the following output:

    Created an instance of StockItem Created another instance of StockItem Now routing the order to billing Now in the after code Date of order origination Fri Jul 07 12:21:48 BST 2006 7 juillet 2006 Calculated order value 670.576 Order is complete, ready for reporting Retrieved order value $670.58 Order has a value of 670.576

    Notice that the date on the sixth line is now in French: "7 juillet 2006".

    Clearly, all the I18N code could be added to another aspect and then invoked from the Charging aspect.


    It's no surprise that Java offers significant I18N support. What is interesting is the programming ease with which this capability can be leveraged. By moving static textual elements into property files, your products can be I18N-enabled, even by end users. This can happen provided you have planned for it in your source code.

    The text formatting examples described here are fairly trivial, but if you wanted to add something more difficult, such as multi-currency interest calculations, then the use of aspects can make such code addition less invasive. Bear in mind that I added the entire reporting mechanism as an aspect.

    While many people may consider AOP risky, it can help fulfill the growing need for LTP. This makes AOP worthy of consideration in certain instances. As programmers, we must always look for ways to improve productivity. Where I18N requires source code changes, you can also use AOP to reduce the risk. In this LTP context, aspects function a little like the code overlay tools of old: Basically, you write your aspect and drop it into your code, thereby minimizing the risk and lowering the cost of legacy code changes.

    As I've shown in this article, the suitability of AOP for applications such as I18N and the requirements of LTP indicate that AOP has come of age as a programming technology.