Tuesday, October 30, 2007

Using addItemAt(Object, int) With ArrayCollection

The Flex ArrayCollection is a handy mechanism for working with Arrays. Its ListCollectionView heritage provides highly useful array manipulation methods such as addItemAt(), setItemAt(), removeItemAt(), and removeAll(). However, there are a few minor issues to be aware of when using ArrayCollection. This blog entry covers one of these -- the need to ensure that the addItemAt() method adds an item at the appropriate location within the ArrayCollection.

If you instantiate an ArrayCollection with an empty constructor, it is important to make an initial addItemAt(Object,int) call on index 0 rather than index 1 or any higher number. If an attempt is made to call ArrayCollection.addItemAt(Object,int) for an index which is greater than the list size, a RangeError is thrown. So, for a newly instantiated ArrayCollection based on an empty array, the only allowable index to call addItemAt(Object,int) is zero [addItemAt(someObject,0)]. Note that this is equivalent to calling ArrayCollection.addItem(Object) with the empty underlying array.

The following ActionScript code snippet illustrates this.


<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
width="750" height="500"
applicationComplete="handleArrayCollection();">

<mx:Script>
<![CDATA[
import mx.controls.Alert;
import mx.collections.ArrayCollection;

/**
* To compile solely this Flex application from the command line, use
* mxmlc -compiler.debug=true ArrayCollectionExample.mxml
*
* This application demonstrates how a RangeError can occur if addItemAt()
* is used at an array index past the end of the Array.
*/

[Bindable] private var example1Array:ArrayCollection =
new ArrayCollection();
[Bindable] private var example2Array:ArrayCollection =
new ArrayCollection();

public function handleArrayCollection():void
{
var index1:int = 0;
for ( index1 ; index1 < 5 ; index1++ )
{
example1Array.addItemAt("item one", index1);
}
Alert.show( "example1Array has " + example1Array.length
+ " items!");

var index2:int = 1;
for ( index2 ; index2 < 5; index2++ )
{
example2Array.addItemAt("item two", index2);
}
Alert.show( "example2Array has " + example2Array.length
+ " items!");
}
]]>
</mx:Script>

</mx:Application>


The above code listing compiles fine, but example2Array has issues at runtime because the for loop that inserts values into it using addItemAt() starts trying to add at index 1 rather than index 0 for this newly allocated ArrayCollection. This results in an error stating: "RangeError: Index '1' specified is out of bounds." Note that the first array, array1example, runs fine because addItemAt() starts by adding at its zero index rather than its one index.

The error message that will be encountered when ActionScript code attempts to addItemAt(1) without ever first creating an item at index 0 is shown here:


RangeError: Index '1' specified is out of bounds.
at mx.collections::ListCollectionView/addItemAt()[C:\dev\flex_201_gmc\sdk\frameworks\mx\collections\ListCollectionView.as:531]
at ArrayCollectionExample/handleArrayCollection()[C:\flexExamples\ArrayCollectionRange\ArrayCollectionExample.mxml:34]
at ArrayCollectionExample/___Application1_applicationComplete()[C:\flexExamples\ArrayCollectionRange\ArrayCollectionExample.mxml:3]


The error contains a longer trace than that shown above, but the important lines are displayed above. The next screen snapshot shows how the error message looks in the debugger:



One other interesting observation can be had here by renaming the source code shown above from a file named ArrayCollectionExample.mxml to ArrayCollection.mxml. Now, with a local application file with the same name as a type used in the application, we see the following types of compiler error messages:

  • "Error: Call to a possibly undefined method addItemAt through a reference with static type ArrayCollection."

  • "Error: Access of possibly undefined property length through a reference with static type ArrayCollection."



The above compiler error messages occur because of the attempt of the code to act upon instances of ArrayCollection and the file with the source code is itself called ArrayCollection. Renaming the file to something like ArrayCollectionExample.mxml fixes the compiler errors.

For additional information on Flex's ArrayCollection, see Flex, Array, ArrayCollection (which talks about using ArrayCollection rather than Array for data binding) and Dave Carabetta's Array Versus ArrayCollection in Flex 2 (brief explanation of difference between Array and ArrayCollection).

Monday, October 29, 2007

Useful Flex/Java Integration Resources

It feels very natural to me to develop applications with Flex for the client-side and have these rich Flash-executed clients run against a back-end developed in Java EE. This blog entry contains useful links and references to resources and articles regarding use of Java with Flex and includes what I like about each resource. This blog entry may be updated occasionally as I come across useful Java/Flex resources. I do not intend to update the date on this entry so that it remains at this link.

Hybridizing Java (30 January 2007)
This Bruce Eckel weblog entry was a wake-up call for many Java developers (myself included) who had previously considered Flash/Flex as not much more than a gimmick platform/language rather than as a real programming platform/language.

Comparing the Syntax of Java 5 and ActionScript 3 (12 November 2006)
This article features a table comparing language syntax of the Java programming language and the ActionScript 3 (used in Flex) language.

Adobe Flex Development Center: Flex and Java
Numerous articles and resources on using Flex and Java, including some of those listed in this entry.

Thirty Minute Flex Test-Drive for Java Developers (20 November 2006)
This example demonstrates using Flex with many Java-oriented favorites including Spring Framework, Hibernate, and Tomcat.

Rich Internet Applications with Adobe Flex 2 and Java (10 May 2006)

Integrating Macromedia Flex with Java (1 December 2004)
This article is an early article covering (then Macromedia) Flex and Java

Data Services Made Easy for Adobe Flex Applications (11 September 2007)
This article outlines a framework for working with two freely available and commonly used means of communication between a Flex front-end and a Java back-end: HTTPService and WebService.

Bridging Flex and Java EE

































Bridging the Java/Flex Gap
Nuances of ActionScript Constructors (contrasted with Java constructors)5 December 2007
More on ActionScript 3 Equality Comparisons24 November 2007
String Comparisons in ActionScript23 November 2007
Overriding Parent get/set Methods and Accessors16 November 2007
ActionScript 3.0 get/set Accessors: A Peek at Java 7 Properties?15 November 2007
Java and ActionScript: switch-case and Switching on Strings8 November 2007
Other Resources on Using Flex and Java Together29 October 2007


This blog entry is meant to be a single location that links to my blog entries regarding working with Flex and Java (particularly Enterprise Java). As such, this entry will be updated each time I add a new blog entry on working with Java and Flex.

Saturday, October 27, 2007

Apache POI: Use HSSFWorkbook to Create New Cell Style

The Busy Developer's Guide to HSSF Features (PDF format) is an excellent resource for learning how to use Apache POI (formerly Jakarta POI) in conjunction with Java applications for manipulating Microsoft Excel files. This document lives up to its name (for Busy Developers) and provides a concise but highly useful introduction to the POI HSSF API. It would be nice if all open source products had guides this easy for beginners to use.

This guide points out (in the Creating Date Cells section) that new cell styles (HSSFCellStyle) must be created from a workbook (HSSFWorkbook) or else any changes to the HSSFCellStyle will impact other (perhaps even ALL) cells in the spreadsheet generated with Apache POI.

For example, the following code may not work as one might think from looking at it:


/**
* Set the style of the supplied cell to be default
* header cell style.
*
* @param aHeaderCell Cell to which default header
* style should be applied.
*/
public static void setDefaultHeaderCellStyle(
final HSSFCell aHeaderCell )
{
final HSSFCellStyle cellStyle = aHeaderCell.getCellStyle();
cellStyle.setAlignment( HSSFCellStyle.ALIGN_CENTER );
cellStyle.setFillPattern( HSSFCellStyle.SPARSE_DOTS );
cellStyle.setBorderBottom( HSSFCellStyle.BORDER_DOUBLE );
cellStyle.setBorderLeft( HSSFCellStyle.BORDER_DOUBLE );
cellStyle.setBorderRight( HSSFCellStyle.BORDER_DOUBLE );
cellStyle.setBorderTop( HSSFCellStyle.BORDER_DOUBLE );
}


From first glance at the above code, it is easy to believe that the various "set" methods called on the passed-in HSSFCell will only impact that particular HSSFCell instance because getCellStyle() was called on that instance. However, this code, as shown, actually impacts all cells on the sheet from which this cell came. To ensure that only specific cells are impacted by a custom cell style, one should get the HSSFCellStyle from the workbook as suggested in the Quick Start Guide.

The following example shows this in action:


/**
* Create a new cell style designed for header-oriented cells.
* The HSSFWorkbook is required to obtain a newly generated
* cell style instance so that cells other than those to which
* this style is set will not be impacted.
*
* @param aWorkbook Workbook for which cell style should apply.
*/
public static HSSFCellStyle createDefaultHeaderCellStyle(
final HSSFWorkbook aWorkbook )
{
final HSSFCellStyle cellStyle = aWorkbook.createCellStyle();
cellStyle.setAlignment( HSSFCellStyle.ALIGN_CENTER );
cellStyle.setFillPattern( HSSFCellStyle.SPARSE_DOTS );
cellStyle.setBorderBottom( HSSFCellStyle.BORDER_DOUBLE );
cellStyle.setBorderLeft( HSSFCellStyle.BORDER_DOUBLE );
cellStyle.setBorderRight( HSSFCellStyle.BORDER_DOUBLE );
cellStyle.setBorderTop( HSSFCellStyle.BORDER_DOUBLE );
return cellStyle;
}


Notice that in the first example, a Cell Style instance is obtained using a getCellStyle method (HSSFCell.getCellStyle()). This gets an already instantiated cell style that applies to many more cells than just the cell upon which the getCellStyle() method was called. This means that any changes to the returned cell style impact all cells tied to that cell style.

In the second example, a completely new instantiation of cell style is obtained with a call to HSSFWorkbook.createCellStyle(). This creates a new instance independent of any cells. In fact, to make this newly instantiated cell style apply to any cells, one must call setCellStyle() on the cell (HSSFCell.setCellStyle()) to which the newly created style should apply.

In essence, there are two opposite approaches to updating cell styles here. The first, getting an existing style from any average cell, works with the assumption that all cells have an existing and same style. Changing that style once therefore changes all the cells' styles. The latter approach, creating a new cell style from a workbook, makes the opposite assumption (that no cells are affected by the newly created style) and cells to which the style should apply must be explicitly associated with the new style.

This significant distinction between acquiring a cell style from a cell or creating a new cell style instance via a workbook is documented in the Quick Start Guide as mentioned above and is not a big issue once it is understood. However, this is the type of issue that is a good example of how important it can be to know more about an API than simply what a favorite IDE lists as available through class and method name completion.

UPDATE (21 December 2007): The HSSFCellUtil Javadoc-based API documentation explains the reason for the different behaviors in acquiring and setting a Cell Style. According to the class description for HSSFCellUtil , Excel has a limitation on the number of styles it can support and so it makes sense to have a single style apply to all cells within a sheet and be controlled by that setting (HSSFCell.setCellStyle()). The HSSFWorkbook.createCellStyle() method actually creates a truly different cell style for individual cell styling needs. The Javadoc method documentation for the HSSFCell.setCellStyle(HSSFCellStyle) method does warn that the passed-in HSSFCellStyle "should be an HSSFCellStyle created/retreived from the HSSFWorkbook."

Thursday, October 25, 2007

My Planned Presentations for RMOUG Training Days 2008

I received notification that Rocky Mountain Oracle Users Group (RMOUG) has accepted two of my abstracts for presentation at RMOUG's Training Days 2008 conference. The presentations are "Introduction to XQuery: A Multipurpose XML Tool" and "Excel with Apache POI and Oracle Database." Training Days will be February 13-14, 2008, at the Colorado Convention Center.

Presentations from several recent RMOUG Training Days conferences are available at the following links:




Other Pages / Blogs on RMOUG Training Days 2008

This portion of this blog entry will be updated regularly as new pages and blogs reference RMOUG Training Days 2008 or RMOUG Training Days in general.

Oracle 2008 Denver Conferences: RMOUG and IOUG Collaborate 2008 (Burleson Consulting)

JL Computer Consultancy Appearances

RMOUG Training Days (W3W3)

Friday, October 19, 2007

Adding Gradients to JFreeChart Charts

In the article Visualize Your Oracle Database Data with JFreeChart published on Oracle Technology Network, Michael Martin and I covered several of JFreeChart’s useful features, but we were unable to cover everything this broad library provides in a reasonably sized article. This blog entry focuses on an aspect of JFreeChart that we did not cover in that article: gradients.

Our third example in the article covered how to use JFreeChart in a Swing-based application and rendered a bar chart for the example. In this blog, I show how easy it is to modify that example to use gradients.

The basic Swing class used to display the JFreeChart-generated bar chart is shown here:


package org.marx.hr.charting;

import java.awt.image.BufferedImage;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;

import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;

/**
* Class that produces simple Swing application to render a JFreeChart-generated bar chart.
*/
public class HrChartingSwingRenderer
{
/**
* Create the GUI and show it. For thread safety,
* this method should be invoked from the
* event-dispatching thread.
*/
private static void createSimpleDemonstrativeLabel(
final String aTitle)
{
HrChartCreator chartCreator = new HrChartCreator();
JFrame frame = new JFrame(aTitle);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

JFreeChart barChart =
chartCreator.createSalaryPerFinanceEmployeeBarChart(
PlotOrientation.VERTICAL );

BufferedImage image =
barChart.createBufferedImage(750,450);
JLabel label = new JLabel();
label.setIcon(new ImageIcon(image));
frame.getContentPane().add(label);

frame.pack();
frame.setVisible(true);
}

public static void main(String[] args)
{
javax.swing.SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
createSimpleDemonstrativeLabel(
"Swing Example Using JFreeChart: "
+ "Salary Per Finance Employee");
}
});
}
}


As shown in this Swing-based class, the HrChartCreator class is used for generating the bar chart. Specifically, the createSalaryPerFinanceEmployeeBarChart method is called on that class. The code listing for that method as used in the article example is shown next:


/**
* Create Bar Chart showing salary of each employee.
*
* @param aOrientation Horizontal or Vertical
* orientation of bar chart.
* @return Bar Chart.
*/
public JFreeChart createSalaryPerFinanceEmployeeBarChart(
final PlotOrientation aOrientation)
{
JFreeChart barChart = null;
public static final String QUERY_SALARY_PER_FINANCE_EMPLOYEE =
"SELECT first_name || ' ' || last_name AS Name, salary " +
"FROM employees " +
"WHERE department_id = 100";

try
{
final CategoryDataset barDataset =
new JDBCCategoryDataset(
databaseAccess.getOracleDbConnection(),
QUERY_SALARY_PER_FINANCE_EMPLOYEE );

barChart =
ChartFactory.createBarChart(
"Finance Department Employees Salaries", // chart title
"Finance Employees",
"Salaries",
barDataset,
aOrientation,
true, // legend displayed
true, // tooltips displayed
false ); // no URLs

}
catch (SQLException sqlEx)
{
System.err.println(
"Error trying to acquire JDBCCategoryDataset.");
System.err.println(
"Error Code: " + sqlEx.getErrorCode());
System.err.println( "SQLSTATE: "
+ sqlEx.getSQLState());
sqlEx.printStackTrace();
}

return barChart;
}


In the example in the article, Figure 7 shows a snapshot of the Swing application with the JFreeChart-generated bar chart. The bars in the bar chart use the default first color (red) for the bars and the bars are solid in color. A gradient could be used here to make the chart a little more interesting and possible even a little more aesthetically pleasing.

Only a few additional lines are needed to add gradients to the bars in this chart. Had we been using a CategoryPlot for other purposes anyway, even fewer individual lines would be needed because we would have already had access to the CategoryPlot and possibly to the BarRenderer. The code listing below shows the method with gradients added. The newly added lines for gradient support are highlighted.


/**
* Create Bar Chart showing salary of each employee.
*
* @param aOrientation Horizontal or Vertical
* orientation of bar chart.
* @return Bar Chart.
*/
public JFreeChart createSalaryPerFinanceEmployeeBarChart(
final PlotOrientation aOrientation)
{
JFreeChart barChart = null;
public static final String QUERY_SALARY_PER_FINANCE_EMPLOYEE =
"SELECT first_name || ' ' || last_name AS Name, salary " +
"FROM employees " +
"WHERE department_id = 100";

try
{
final CategoryDataset barDataset =
new JDBCCategoryDataset(
databaseAccess.getOracleDbConnection(),
QUERY_SALARY_PER_FINANCE_EMPLOYEE );

barChart =
ChartFactory.createBarChart(
"Finance Department Employees Salaries, // chart title
"Finance Employees",
“Salaries”,
barDataset,
aOrientation,
true, // legend displayed
true, // tooltips displayed
false ); // no URLs

final CategoryPlot plot = barChart.getCategoryPlot();
final BarRenderer renderer = (BarRenderer) plot.getRenderer();
final GradientPaint gradient =
new GradientPaint( 0.0f, 0.0f, Color.RED.brighter(),
0.0f, 0.0f, Color.WHITE );
renderer.setSeriesPaint(0, gradient);

}
catch (SQLException sqlEx)
{
System.err.println(
"Error trying to acquire JDBCCategoryDataset.");
System.err.println( "Error Code: "
+ sqlEx.getErrorCode());
System.err.println( "SQLSTATE: "
+ sqlEx.getSQLState());
sqlEx.printStackTrace();
}

return barChart;
}


The new version of the bar chart with gradients is shown next (click on image to see larger version):



With just a few extra lines of code we were able to easily enhance our bar chart to use a gradient rather than solid color for its bars.

Thursday, October 18, 2007

When An ActionScript Object Does Not Provide toString()

In my last blog entry, I provided some basic code that demonstrated some differences between using the ObjectUtil.toString() method to provide a textual representation of an ActionScript object's contents versus using an ActionScript object's own toString() implementation. I mentioned that ObjectUtil.toString() is an especially useful approach to see the contents of an object when that object does not have an explicit toString() method defined.

This blog entry shows the output provided by trace() from the example code in the last blog entry when the Person.toString() method has been commented out. In other words, this screenshot shows the differences between using ObjectUtil.toString() and objects' implicit toString() representation when the class has not explicitly defined toString().



As would be expected, the object-level representation is significantly less useful than the example in my last blog entry where Person's last name, first name, and age are printed out by toString(). The XMLList examples, not surprisingly, are exactly the same as in my last entry because they are based on hard-coded XML in the source code rather than on the Person.as class.

Perhaps the most interesting observation from all of this is that ObjectUtil.toString() presents the same basic information on an ActionScript object regardless of whether it has implemented a toString() method or not. This points to one of the advantages of using ObjectUtil.toString(), especially on ActionScript objects with questionable or no toString() implementations.

Side Note #1

I noted this in the last blog, but think it is worth mentioning again. When an ActionScript class explicitly defines a toString() method, that toString() method can be explicitly called by clients. However, if a toString() method is not explicitly defined for a particular ActionScript class, then toString() can only be implicitly called. This is unlike Java, where a toString() method can be called (even if its results are meaningless) on any object regardless of whether or not that Java class has specifically overridden toString().

Side Note #2

Another interesting observation brought up in my previous blog is that the methods defined in ActionScript's root Object class are overridden by first-level descendant classes by simply implementing functions with the same signature as in the Object class. This is different than ActionScript's normal use of "override" keyword to override methods of a parent class. In all cases, overridden methods must match the signature of the parent class. ActionScript does not allow for overloading of methods based on argument types or number of arguments.

Wednesday, October 17, 2007

Flex: ObjectUtil.toString() Versus Inherited Object's toString()

When using trace, Alert.show, or so other method for logging in Flex applications, it is often convenient to print out the representation of ActionScript objects. This blog entry compares two common methods for printing out a Flex object's representation.

ActionScript 3.0 (part of Flex 2) provides an "all static class" called ObjectUtil that provides a number of enormously helpful static functions. These useful functions include introspection; deep object copying; comparing objects, strings, and numbers; and printing out "pretty" string representations of objects (the focus of this blog entry).

The second approach to obtaining a textual representation of an ActionScript object is to use the Java-like toString() method all ActionScript objects can override from the Object class. As with Java, these individual toString() methods are most useful when called on instances of classes for which the toString() method has been specifically overridden for that class. In fact, in ActionScript, a descendant class must override toString() if it is to be called explicitly on that class.

As a side note, the ActionScript Object.toString() does not work exactly like Java's Object.toString(). For example, if a toString() method is not explicitly defined on a child ActionScript class, no toString() function can be called directly and explicitly on that class. Attempting to do so results in an error message: "Error: Call to a possibly undefined method toString through a reference with static type ..." In Java, if a toString() method is not explicitly defined, it can still be called because an inherited version can be called, even if it is on the root class Object. However, even in ActionScript, implicit uses of toString on an object are allowable even when no toString() has been formally defined for an ActionScript object. For example, if I commented out the toString() function in the Person.as code shown below and had declared a Person object variable named aPerson, then trace(aPerson) would be allowable, but trace(aPerson.toString()) would not be allowable.

It is worth noting that while most ActionScript classes must use the "override" keyword to explicitly override a parent class' method, this is not the case for the static functions defined in the Object class. Instead, these functions are overridden in each first descendant class using a simple redefinition of the function with the same signature. The code example below (Person.as) shows how toString() can be overridden (note without the override keyword) by a descendant class.

So, why would one use ObjectUtil.toString() rather than the toString() method that all ActionScript classes have access to (whether their own overridden version or a version inherited from an ancestor class)?

One reason for preference of ObjectUtil.toString() might be because no useful toString() method has been implemented for a specific class of interest. As described above, toString() cannot be invoked on an ActionScript object unless that class has explicitly overridden toString().

Another reason to prefer ObjectUtil.toString() might be that the the toString() method implemented for a specific class may lack the information provided by the ObjectUtil.toString() method on that same class.

A third situation in which ObjectUtil.toString() may be more useful than individual class toString() methods is the case where there are many nested classes (has-a relationships) and some of the constituent parts do not have implemented toString() methods or the outer/composite class' toString() method does not make use of composing class' toString() methods.

The following relatively simple ActionScript class works as an example of some of the differences in output using mx.utils.ObjectUtil.toString() as compared to using toString as defined on objects themselves. This example actually puts the "best foot forward" for individual object toString() implementations because the objects used in this example do have relevant implementations of toString(). If these objects did not have their own relevant toString() implementations, the output information from the objects using their own toString() implementations would be nowhere near as useful as ObjectUtil.toString()'s output for those objects.

Main MXML Application File: ToStringTester.mxml


<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
width="250" height="250"
applicationComplete="runTests(event)">

<mx:Script>
<![CDATA[
/*
* To compile this application from the command line, use
* mxmlc -compiler.debug=true ToStringTester.mxml
*
* This needs to be compiled with debug on and a debug
* Flash player needs to be used when loading application
* so that trace calls will be recorded.
*/

import mx.collections.ArrayCollection;
import mx.utils.ObjectUtil;
import example.tostring.Person;

public function runTests(aEvent:Event):void
{
printHeader("OBJECT");
const person1:Person = buildInstanceOfPerson("Dillon", "Frank", 19);
printObjectWithStaticToString(person1);
printObjectWithItsOwnToString(person1);

printHeader("SIMPLE STRING ARRAY");
const simpleArray:Array = new Array( "blue", "green", "red", "yellow" );
printArrayWithStaticToString( simpleArray );
printArrayWithItsOwnToString( simpleArray );

printHeader("OBJECT ARRAY");
const person2:Person = buildInstanceOfPerson("Smith", "Wesley", 35);
const personArray:Array = new Array( person1, person2 );
printArrayWithStaticToString( personArray );
printArrayWithItsOwnToString( personArray );

printHeader("ARRAY COLLECTION");
const personArrayCollection:ArrayCollection =
new ArrayCollection( personArray );
printArrayCollectionWithStaticToString( personArrayCollection );
printArrayCollectionWithItsOwnToString( personArrayCollection );

printHeader("XMLLIST");
const xmlPeople:XMLList = buildPeopleXml().Person;
printXMLListWithStaticToString( xmlPeople );
printXMLListWithItsOwnToString( xmlPeople );
printXMLListWithItsOwnToXMLString( xmlPeople );
}

public function printObjectWithStaticToString(aPerson:Person):void
{
trace( "--- Display object using static ObjectUtil.toString():" );
trace( ObjectUtil.toString(aPerson) );
}

public function printObjectWithItsOwnToString(aPerson:Person):void
{
trace( "--- Display object using its own toString(): " );
trace( aPerson ); // using implicit call to toString()
}

public function printArrayWithStaticToString(aArray:Array):void
{
trace( "--- Display array using static ObjectUtil.toString(): " );
trace( ObjectUtil.toString(aArray) );
}

public function printArrayWithItsOwnToString(aArray:Array):void
{
trace( "--- Display array using its own toString(): " );
trace( aArray.toString() );
}

public function printArrayCollectionWithStaticToString(
aArray:ArrayCollection):void
{
trace( "--- Display array collection using static ObjectUtil.toString(): " );
trace( ObjectUtil.toString(aArray) );
}

public function printArrayCollectionWithItsOwnToString(
aArray:ArrayCollection):void
{
trace( "--- Display array collection using its own toString(): " );
trace( aArray.toString() );
}

public function printXMLListWithStaticToString(aXml:XMLList):void
{
trace( "--- Display XMLList using static ObjectUtil.toString(): " );
trace( ObjectUtil.toString(aXml) );
}

public function printXMLListWithItsOwnToString(aXml:XMLList):void
{
trace( "--- Display XMLList using its own toString(): " );
trace( aXml.toString() );
}

public function printXMLListWithItsOwnToXMLString(aXml:XMLList):void
{
trace( "--- Display XMLList using its own toXMLString(): " );
trace( aXml.toXMLString() );
}

public function printHeader(aHeader:String=""):void
{
trace("------------------------------------");
trace(" " + aHeader );
trace("------------------------------------");
}

public function printBreaker():void
{
trace("------------------------------------");
}

public function buildInstanceOfPerson( aLastName:String,
aFirstName:String,
aAge:uint ):Person
{
var person:Person = new Person();
person.setLastName(aLastName);
person.setFirstName(aFirstName);
person.setAge(aAge);
return person;
}

public function buildPeopleXml():XML
{
var person:XML = <People>
<Person lastName="Johnson" firstName="Jim" age="45" />
<Person lastName="Smith" firstName="John" age="50" />
<Person lastName="Rhodes" firstName="Julie" age="75" />
</People>;
return person;
}
]]>
</mx:Script>

<mx:VBox id="mainBox">
<mx:Label text="See trace() Output" />
</mx:VBox>

</mx:Application>



In the above application, the ActionScript class example.tostring.Person is referenced and used. This code for this user-defined class is shown next.


Class Used in toString Examples: example.tostring.Person



package example.tostring
{
public class Person
{
private var lastName:String;
private var firstName:String;
private var age:uint;

public function Person()
{
}

public function getLastName():String
{
return lastName;
}
public function setLastName(aLastName:String):void
{
lastName = aLastName;
}

public function getFirstName():String
{
return firstName;
}
public function setFirstName(aFirstName:String):void
{
firstName = aFirstName;
}

public function getAge():uint
{
return age;
}
public function setAge(aAge:uint):void
{
age = aAge;
}

/**
* Return String representation of me.
*
* @return String representation of me.
*/
public function toString():String
{
return "Last Name: " + lastName + "; "
+ "First Name: " + firstName + "; "
+ "Age: " + age;
}
}
}



After this simple application (MXML file and the ActionScript Person.as file) has been compiled into ToStringTester.swf, it should be run in a debug Flash Player with the debugger turned on. When this is done, the trace() output will be displayed in the debugger window and will look something like that shown in the screen snapshot below.




As the trace output indicates, ObjectUtil.toString() can be useful for providing additional information above and beyond that provided by an object's toString(). Had toString() not been implemented for the Person class, the advantage of ObjectUtil.toString() would have been even more apparent.

For the string representation of individual objects, ObjectUtil.toString() was not much more useful in this particular case. However, the array representation provided by ObjectUtil.toString() was superior to the individual array's toString() because it provided the array index numbering in square brackets before each array element. Interestingly, the XMLList representation is identical for the two approaches and even for the third approach of using the XMLList-specific toXMLString() method.

ObjectUtil.toString() provides a global, static function that allows for useful representation of ActionScript objects to be obtained even when the implementor of the ActionScript object has not implemented that object's toString() method. This is especially useful for complex objects that consist of many composing parts such as other objects, arrays, and other data structures.

Related Blogs and Articles

Monday, October 15, 2007

OpenLaszlo with Java Servlets 2.5 and Resource Injection

OpenLaszlo provides a compelling solution for building highly flexible, rich, and robust web clients that are supported on the back end by business logic and data tier implemented with Java EE (servlets and EJB3/JPA for example). OpenLaszlo supports HTTP access of the back-end servlets and the OpenLaszlo servlet download makes it very simple to incorporate OpenLaszlo with Java servlets and JSPs.

Java EE 5 uses Java annotations introduced by Java SE 5 to make development and configuration of Java EE 5 software easier for the developer. One such use of these annotations in Java EE is to use the @EJB injection in a servlet to access an Enterprise JavaBean (EJB). However, to use the @EJB annotation in a servlet, the servlet must be executed in a Java Servlet 2.5 environment.

Section 5.1 of the System Administrator's Guide to Deploying OpenLaszlo Applications covers how to implement "OpenLaszlo-enabled web applications" and Section 5.2 covers how to create a minimal OpenLaszlo server. In either case, it is likely that a developer would start with the web.xml provided with the OpenLaszlo distribution. There is no issue with this unless the developer wants to use Servlet 2.5 support for annotations.

To run Java 2.5 servlets with annotations (such as the @EJB) in conjunction with OpenLaszlo, the web.xml file that comes as part of the OpenLaszlo servlet distribution (as of 4.0.6) should be changed from being a Servlet 2.2 web.xml file to a Servlet 2.5 web.xml file. The web.xml file that is delivered with the OpenLaszlo distribution (as of 4.0.6) begins as shown (Servlet 2.2 web.xml format):


<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
"http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">


For a Servet 2.5-compatible web application, the Servlet 2.5 web.xml file should be changed to start as follows:


<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee">


Note that the web.xml file for Servlet 2.2 is defined by a DTD while the web.xml file for Servlet 2.5 is defined by XML Schema. Also note that there should be no carriage return between the http://java.sun.com/xml/ns/javaee and the http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd in the Servlet 2.5 web.xml, but I used one above to allow it to fit better on the page.

Changing the web.xml file that is delivered with OpenLaszlo as part of its servlet distribution from 2.2 to 2.5 allows servlets accessed from within the same WAR file to make use of annotations and the injections that come with annotations. An alternative approach would be to bundle EJB-accessing servlets or servlets needing annotations in a separate WAR from the WAR assembled from OpenLaszlo-delivered components.

Saturday, October 13, 2007

Highly Useful JAX-WS Introductions

As I have been learning about and working with the Java API for XML Web Services (JAX-WS), there have been several highly useful tutorials and introductions to this specification and its reference implementation. I list all of them for easy future reference and will then highlight one resource in particular from this list that I have found to be especially helpful.

I have found the following JAX-WS introductions to be especially useful:


These are all good resources for background and an introduction to web services with JAX-WS, but I have found the one listed first to be especially useful in a GlassFish environment. The main differentiator between this article and many other fine articles on the subject is that the author of this article provided the command-line syntax for running javac and the various GlassFish commands (such as asadmin commands and the wsgen and wsimport commands).

Many introductions provide Ant targets for the reader to run or illustrate how to use a specific IDE to do things (which this article does as well), but it is nice to have an author go a little further and actually provide the low-level command-line syntax to use. This allows developers who almost always seem to be in a hurry to get right at the new material without needing to make sure that the appropriate Ant build.xml file is downloaded. It also frees the developer from being required to use the IDE the author used. In this case, the developer would still be required to use GlassFish to make use of these command-line examples, but that is unavoidable due to the proprietary nature of these administrative tools.

I have also followed with interest the debate about web services development with JAX-WS with a bottom-up approach (annotation Java classes and generating appropriate artifacts) versus with a top-down approach (starting with WSDL). The article How to Develop a JAX-WS Service from a Web Service Description Language (WSDL) makes a case for using the top-down approach even though the author acknowledges that it is simpler to use the bottom-up approach. I can see where there are advantages to using the top-down approach for large, complex systems maintained by many developers, but I really like the bottom-up approach for smaller applications, demonstrations, and prototypes.

Not everyone is happy with JAX-WS. Richard Monson-Haefel has posted (2006) two blogs (JAX-WS is Bad, Bad! and Redeemed! JAX-WS Still Sucks) describing things he finds unsatisfactory about JAX-WS.

UPDATE (10 November 2007): Made some minor cosmetic changes and spelling fixes, but did not change anything of substance.

Thursday, October 11, 2007

Java and Ant Properties Refresher

I occasionally need a certain built-in Java property for use in code or in an Apache Ant script and this blog entry is intended to make an easy-to-remember location for me to find a list of some of the properties that I use most often.

Some of the Java properties available from the call to System.getProperties() are listed in the Java Tutorial on System Properties. This resource lists only a small subset of the available properties, but the listed properties are among the most significant and more frequently used of the properties. These significant properties include file.separator, line.separator, path.separator, user.dir, and user.home.

The Javadoc API documentation for the System class has even more properties listed than the Java Tutorial, but not all of the available properties are listed (such as user.country, user.language, and user.timezone).

One of the best ways to view all of the available properties is to call System.getProperties() and iterate over the returned properties to see what is available. The code below shows how this is done and takes advantage of some Java SE 6 features in the process.

Java Code Listing for One Way to Display Available Properties

package properties;

import java.util.Enumeration;
import java.util.Properties;
import java.util.Set;

/**
 * Lists all Java system properties.  Uses
 * Properties.stringPropertyNames for convenience,
 * but this is a Java SE 6 feature and it shows only
 * properties in which both the key and the value
 * are String types.
 */
public class SystemPropertiesExample
{
  public static void main(final String[] aArgs)
  {
     System.out.println(
        "Welcome!  Here are your Java System properties:");
     Properties systemProperties = System.getProperties();
     Set propertyNames =
        systemProperties.stringPropertyNames();
     for ( final String propertyName : propertyNames )
     {
        String propertyValue =
           systemProperties.getProperty(propertyName);
        if ( propertyValue.isEmpty() )
        {
           propertyValue =
              "<<<empty string>>>";
        }
        System.out.println(  propertyName
                           + " = "
                           + propertyValue );
     }
  }
}

Of course, Java developers have been iterating over available properties since the beginning days of Java using java.util.Enumeration (not related to the enum introduced in Java 5) with the Properties.propertyNames() method, but the above code example demonstrates the new-to-Java-SE-6 Properties.stringPropertyNames() method in action.

Ant also makes it easy to access the Java system properties in addition to Ant's own built-in properties. The build.xml excerpt below demonstrates how easy it is to use Ant to display custom properties (defined in a separate build.properties file in this case), Ant’s built-in properties, Java-related properties, and even Java implementation-specific properties.

An Ant build.xml File that Displays Available Properties

<project name="SystemPropertiesExample"
default="buildJar"
basedir=".">

<property file="build.properties" />

<tstamp />

<target name="init">
<mkdir dir="${classes.dir}" />
</target>

<target name="clean">
<delete dir="${classes.dir}" />
</target>

<target name="compile"
description="Compile Java class for Java System.properties example"
depends="init">
<javac srcdir="${src.dir}"
destdir="${classes.dir}"
includes="properties/SystemPropertiesExample.java"
debug="${javac.debug}"
deprecation="${javac.deprecation}"
verbose="${javac.verbose}" />
</target>

<target name="buildJar"
description="Build executable JAR to display Java System.properties"
depends="compile">
<jar destfile="${dist.dir}/${jar.system.properties}"
basedir="${classes.dir}"
includes="properties/SystemPropertiesExample.class">
<manifest>
<attribute name="Main-Class"
value="properties.SystemPropertiesExample" />
</manifest>
</jar>
</target>

<target name="echo"
description="Display custom and standard available properties">
<antcall target="echoCustomProperties" />
<antcall target="echoAntBuiltInProperties" />
<antcall target="echoJavaSystemProperties" />
<antcall target="echoSunSpecificProperties" />
</target>

<!-- Properties created specifically for use in this build file (see the
associated build.properties file. -->
<target name="echoCustomProperties"
description="Display properties custom to this build file">
<echo message="------------- CUSTOM PROPERTIES ---------------" />
<echo message="jar.system.properties = ${jar.system.properties}" />
<echo message="javac.debug = ${javac.debug}" />
<echo message="javac.deprecation = ${javac.deprecation}" />
<echo message="javac.verbose = ${javac.verbose}" />
<echo message="classes.dir = ${classes.dir}" />
<echo message="dist.dir = ${dist.dir}" />
<echo message="src.dir = ${src.dir}" />
</target>

<!-- Ant's built-in properties as described in Ant manual at
http://ant.apache.org/manual/using.html#built-in-props. -->
<target name="echoAntBuiltInProperties"
description="Display Ant's built-in properties">
<echo message="------------- ANT PROPERTIES ---------------" />
<echo message="ant.file = ${ant.file}" />
<echo message="ant.home = ${ant.home}" />
<echo message="ant.java.version = ${ant.java.version}" />
<echo message="ant.project.name = ${ant.project.name}" />
<echo message="ant.version = ${ant.version}" />
<echo message="basedir = ${basedir}" />
</target>

<!-- Ant's peek at select Java's properties. Ant actually can see many more
properties than shown here, including Sun-specific properties when using
that particular JVM, but only a sample is shown here. -->
<target name="echoJavaSystemProperties"
description="Display Java properties">
<echo message="------------- (SELECT SAMPLE OF) JAVA PROPERTIES ---------------" />
<echo message="java.class.path = ${java.class.path}" />
<echo message="java.class.version = ${java.class.version}" />
<echo message="java.home = ${java.home}" />
<echo message="java.library.path = ${java.library.path}" />
<echo message="java.runtime.name = ${java.runtime.name}" />
<echo message="java.runtime.version = ${java.runtime.version}" />
<echo message="java.vendor.url = ${java.vendor.url}" />
<echo message="java.vendor.url.bug = ${java.vendor.url.bug}" />
<echo message="java.version = ${java.version}" />
<echo message="java.vm.info = ${java.vm.info}" />
<echo message="java.vm.name = ${java.vm.name}" />
<echo message="java.vm.specification.vendor = ${java.vm.specification.vendor}" />
<echo message="java.vm.vendor = ${java.vm.vendor}" />
<echo message="java.vm.version = ${java.vm.version}" />
<echo message="os.arch = ${os.arch}" />
<echo message="os.name = ${os.name}" />
<echo message="os.version = ${os.version}" />
<echo message="user.country = ${user.country}" />
<echo message="user.home = ${user.home}" />
<echo message="user.language = ${user.language}" />
<echo message="user.name = ${user.name}" />
<echo message="user.timezone = ${user.timezone}" />
</target>

<!-- Display Ant's peek at select Sun-specific properties. -->
<target name="echoSunSpecificProperties" if="sun.management.compiler">
<echo message="--------------- (SELECT) SUN-SPECIFIC PROPERTIES ---------------" />
<echo message="sun.boot.library.path = ${sun.boot.library.path}" />
<echo message="sun.cpu.isalist = ${sun.cpu.isalist}" />
<echo message="sun.java.launcher = ${sun.java.launcher}" />
<echo message="sun.management.compiler = ${sun.management.compiler}" />
</target>

</project>

The build.properties file used by the build.xml file above


# Directories
classes.dir = classes
dist.dir = dist
src.dir = src

# Generated Files
jar.system.properties = displayJavaProperties.jar

# Java Compiler (javac) Attributes
javac.debug = off
javac.deprecation = off
javac.verbose = no

UPDATE (6 November 2007): This <echoproperties /> task is an even easier way to display properties available to an Ant build.xml file. I cover this useful Ant task in more detail in this blog entry.

There are some differences between the properties returned from the Java System.getProperties() call and the Java-related properties accessed in Ant. For example, the property java.class.path has a value shown only of the executable JAR file that was executed with "java –jar displayJavaProperties.jar" to print out the properties while the Ant java.class.path display shows a very long classpath value with paths pointing to various Java and Ant JAR and ZIP files.

Fred Swartz has a nice discussion of using Java's System.getProperties with Swing in his Java: Systems Properties page.

Monday, October 8, 2007

JAXBContext Issues

I really like the Java API for XML Binding (JAXB). While there are many specifications and technologies that seem oversold or otherwise disappointing, JAXB repeatedly proves itself useful, easy to work with, and lives up to its hype. That being stated, there are times when working with the JAXB reference implementation (RI) has been slightly less straightforward than I would have hoped. This blog entry summarizes a few related issues that I have run into when using JAXB and how I got around them.

As shown in the "Java Web Services Tutorial" section covering Basic Examples of Using JAXB, a JAXBContext is obtained by calling JAXBContext.newInstance(). In the examples on this page, all calls to JAXBContext.newInstance are passed a single argument (a String representing a context path). For many applications using JAXB RI, this approach to acquiring a JAXBContext is sufficient.

Unfortunately, I have run into situations where I have needed to use a different version of JAXBContext.newInstance() to get my applications to work properly without exception. As shown in the Java EE 5 Javadoc documentation for JAXBContext, there are five different versions of the newInstance method. The version that I often need to use to avoid exceptions is the newInstance method that accepts the String as a context path and accepts a ClassLoader. For the second argument, the ClassLoader, it seems best and easiest to use ClassLoader.getSystemClassLoader().

Even when explicitly passing in a ClassLoader to the newInstance method, I have observed other conditions that can cause a JAXBException to occur when trying to obtain the JAXBContext. One of these issues is the conflict between the JAXB 2.0 RI that comes with Java SE 6 and the JAXB 2.1 that comes with the GlassFish Java EE application server reference implementation. The conflict is covered in jaxb: Unofficial JAXB Guide - Migrating JAXB 2.0 Applications to Java SE 6, which explains different ways to resolve the problem. I think that the easiest method is to simply copy the appropriate libraries into the JRE endorsed area. Kohsuke Kawaguchichi's blog entry JAXB 2.1 released also covers the issue of running a JAXB 2.1 implementation (such as comes with GlassFish v2) in a Java SE 6 environment and mentions using the Java Endorsed Standards Override Mechanism. The document Which JAXB RI is Included in Which JDK provides some useful information as well.

Rama Pulavarthi covers a similar problem (and an endorsed libraries fix related to use of JAXB and JAX-WS) in his blog entry Problems Using JAX-WS 2.1 and JAXB 2.1 with Java 6.

Finally, even with an appropriate class loader explicitly passed to JAXBContext.newInstance and the proper libraries from GlassFish copied into my JRE's endorsed libraries folder, I have occasionally still run into problems trying to obtain a JAXBContext. When all of the previous recommendations have been handled, the problem has usually been conflicting versions of Java JRE on my machine. To fix this on a Windows machine, I have ensured that only one version of Java (one JDK and one JRE directory) reside in the "Program Files/Java" directory. Also, I have ensured that no other conflicting Java versions, such as in System32 directory, are causing problems.

To summarize, exceptions encountered when trying to obtain a JAXBContext can usually be resolved by taking the following steps:

  1. Ensuring that JAXB implementation classes are in my classpath. This is not covered previously in this blog and should not usually be an issue anymore (in Java SE 6) because the JAXB reference implementation comes with Java SE 6.

  2. Explicitly pass a classloader to the JAXBContext.newInstance method to avoid confusion about the appropriate class loader.

  3. Copying appropriate endorsed libraries from GlassFish directory to Java SE JRE endorsed libs directory when using GlassFish with JAXB 2.1 and Java SE 6.0 with JAXB 2.0.

  4. Removing old versions of Java from system or at least moving old, unused versions into directories other than normal location of Java installation.



This blog entry may make use of JAXB RI sound more difficult than it normally is. I have used this entry to capture my experience and lessons learned from working with the JAXB RI, but overall I have been very happy with it. I have used JAXB in many different situations and find it extremely easy to use in most cases. One nice use of JAXB is described in my OTN (Oracle Technology Network) article Better JPA, Better JAXB, and Better Annotations Processing with Java SE 6.

Sunday, October 7, 2007

My Publications

The following publications are listed in reverse chronological order (with the exception of those at the bottom which do not have a specific release date):

Build a Java Application with Eclipse, Spring, and Oracle WebLogic Server
http://www.oracle.com/technology/pub/articles/marx-oepe-spring.html
Oracle Technology Network, February 2010

Bringing Web to the Desktop with Adobe AIR
Rocky Mountain Oracle Users Group Training Days 2009
12 February 2009

REST from Web Services Frustrations
Rocky Mountain Oracle Users Group Training Days 2009
12 February 2009

Java EE and Flex: A Compelling Combination, Part 2
http://www.javaworld.com/javaworld/jw-02-2009/jw-02-javaee-flex-2.html
5 February 2009

Java EE and Flex: A Compelling Combination, Part 1
http://www.javaworld.com/javaworld/jw-01-2009/jw-01-javaee-flex-1.html
13 January 2009

Apply Flash to Java: Flex and OpenLaszlo
Colorado Software Summit 2008
19-24 October 2008

Java Management Extensions (JMX) Circa 2008
Colorado Software Summit 2008
19-24 October 2008

Basic Java Persistence API Best Practices
http://www.oracle.com/technology/pub/articles/marx-jpa.html
Oracle Technology Network
May 2008

Add Flash to Your Oracle DB Applications: Flex and OpenLaszlo
Collaborate08 IOUG Forum
14 April 2008

Introduction to XQuery: A Multipurpose XML Tool
Rocky Mountain Oracle Users Group Training Days 2008
14 February 2008

Excel with Apache POI and Oracle Database
Rocky Mountain Oracle Users Group Training Days 2008
13 February 2008

Visualize Your Oracle Database Data with JFreeChart
http://www.oracle.com/technology/pub/articles/marx-jchart.html
Oracle Technology Network, September 2007, with Michael G. Martin

Better JPA, Better JAXB, and Better Annotations Processing with Java SE 6
http://www.oracle.com/technology/pub/articles/marx-jse6.html
Oracle Technology Network
September 2007

Ruby on Rails Revisited (presentation and paper)
Rocky Mountain Oracle Users Group Training Days 2007
15 February 2007

Ajax: A Real Modern-day Web Development Hero (presentation and paper)
Rocky Mountain Oracle Users Group Training Days 2007
14 February 2007

Java Persistence API: One API to Rule Them All and in Java Persist Them (paper)
Rocky Mountain Oracle Users Group Training Days 2007

Accessorize Oracle Database with Ruby
http://www.oracle.com/technology/pub/articles/marx-ruby.html
Oracle Technology Network, February 2007

Riding Rails to Ruby and Riches (paper and presentation)
Rocky Mountain Oracle Users Group Training Days 2006
15 February 2006

Add Some Spring to Your Oracle JDBC
http://www.oracle.com/technology/pub/articles/marx_spring.html
Oracle Technology Network, November 2005

Using JDeveloper CodeCoach
SQL>UPDATE_RMOUG, Winter 2005, pp. 8-13

Aspect-Oriented Programming with JDeveloper Plug-in (co-author presentation and paper)
Rocky Mountain Oracle Users Group Training Days 2005
11 February 2005

Better Java with JDeveloper 10g (CodeCoach, Code Advice, Code Metrics, Profilers presentation and paper)
Rocky Mountain Oracle Users Group Training Days 2005
10 February 2005

Navigating the J2EE Jungle: An OC4J Grab Bag (co-author/presenter)
Rocky Mountain Oracle Users Group Training Days 2004
12 February 2004

To Oracle XML and Beyond: PDF and Pictures (Oracle XML, XSL-FO, SVG presentation and paper)
Rocky Mountain Oracle Users Group Training Days 2004
11 February 2004

More JSP Best Practices
http://www.javaworld.com/javaworld/jw-07-2003/jw-0725-morejsp.html
JavaWorld
25 July 2003

Beyond the Buzzwords: Walking the Talk, Part III (J2EE/XML presentation and paper)
Rocky Mountain Oracle Users Group Training Days 2003
6 March 2003

An Overview of Web Technologies for Oracle-Driven Web Sites
http://www.geocities.com/dustinmarx/SW/rmoug/OracleWeb-200612reprint.pdf
SQL>UPDATE_RMOUG, Summer 2002, pp. 22-28.

An Overview of Object-Relational Mapping (co-author/presenter presentation and paper)
Rocky Mountain Oracle Users Group Training Days 2002
21 February 2002

JSP Best Practices
http://www.javaworld.com/javaworld/jw-11-2001/jw-1130-jsp.html
JavaWorld
29 November 2001



The publications below this point do not have a single release date:


The Conventions of Rails
http://www.geocities.com/dustinmarx/SW/rails/conventions.html
(no longer available since late 2009 demise of GeoCities)

Common Struts Errors and Their Causes
http://sites.google.com/site/dustinmarx/home/struts_errors-html
(Previously available at http://www.geocities.com/dustinmarx/SW/struts/errors.html prior to the late 2009 demise of GeoCities)



Dilbert.com