Saturday, October 6, 2007
CachedRowSet class documentation
Four ways to scroll though records.
An example of a stored procedure that can return a subset of a ResultSet can be found here.
This is interesting in that it illustrates (see method #3) how to create a subset without temp tables. Although it initially selects all records in the table, marshalling a huge ResultSet object out of the DB Server does not occur.
The downside is that there is no way to determine that the last record in the subset being returned might represent the last record in the table, preventing messages being issued to the user that says something like "You have reached the end of file." or "No records exist beyond this page." or perhaps to remove the "Next Page" navigation button from the JSP.
For perphaps a better strategy, read the posting about CachedRowSet, above.
This is interesting in that it illustrates (see method #3) how to create a subset without temp tables. Although it initially selects all records in the table, marshalling a huge ResultSet object out of the DB Server does not occur.
The downside is that there is no way to determine that the last record in the subset being returned might represent the last record in the table, preventing messages being issued to the user that says something like "You have reached the end of file." or "No records exist beyond this page." or perhaps to remove the "Next Page" navigation button from the JSP.
For perphaps a better strategy, read the posting about CachedRowSet, above.
Friday, August 24, 2007
Log4J - Extending the Appender classes to wrap messages
Here's the problem: I want my Log4J output (hell, any logging output, Log4J or otherwise)to be easy to look at. I don't want the format to be "busy". I want things to line up so that the information I'm looking for is easily spotted. I don't think I'm alone in this idea and to drive this point home, it's partially the reason why Log Factor 5 and Chainsaw exists, in my opinion.
To illustrate, this is what I'm talking about as motivation to get order into log files.

I don't know about you, but looking at that is not my idea of a good time, especially if it happens to be 4:00AM. All I'm asking for is a little order in my chaos.
Formatting the message body
I'm not going to go into the detail surrounding Log4J message formatting. You can get that information from here.
What I am referring to is how to use the formatting instructions presented at the above link to help accomplish a more formatted message body.
*NOTE*
The example log-files presented below will not be properly formatted if you are using Internet Explorer. However, when using Firefox the formatting is presented properly. (You're missing out if you're using I.E.)
Limiting the message body to a certain length so as to maintain a degree of uniformity serves to help the ol' Mark-IV eyeball make sense out of the log entries. To illustrate, shown below is the log-file layout that tries to maintain a certain place for each logging element and where the message body is limited to say- - - 80-bytes.
But what about when the message body is more than 80-bytes? That would hork-up the "pretty factor", huh? Now it was apparent that when a message body is more than 80 bytes, it should be wrapped to the next line, BUT, maintain the same information about where the logging event (line number, mainly) took place AND the developer should not have to deal with that issue. All the developer should do is what they were used to doing in the first place... mainly this:
The whole solution
Extend the ConsoleAppender class in log4j. The idea being pursued here is to intercept the message, testing the message body length and if greater than 80-bytes, breaking up the message body into 80-byte segments and reconstituting the message segment into a new LoggingEvent object. Simple, really.
Now that the class is created, place it into any package of your project. Next, modify your log4j.xml segment that describes the use of the ConsoleAppender as shown below.

Now, whenever the developer logs a message longer than 80-bytes:
Instead of this message formatting .... (some messages appear before and after)
... the message is formatted like this.
You can take the same FoldingConsoleAppender class above and use it as the basis to extend Log4J's FileAppender and DailyRollingFileAppender.
Thanks to Log4J's open source position !
To illustrate, this is what I'm talking about as motivation to get order into log files.

I don't know about you, but looking at that is not my idea of a good time, especially if it happens to be 4:00AM. All I'm asking for is a little order in my chaos.
Formatting the message body
I'm not going to go into the detail surrounding Log4J message formatting. You can get that information from here.
What I am referring to is how to use the formatting instructions presented at the above link to help accomplish a more formatted message body.
*NOTE*
The example log-files presented below will not be properly formatted if you are using Internet Explorer. However, when using Firefox the formatting is presented properly. (You're missing out if you're using I.E.)
Limiting the message body to a certain length so as to maintain a degree of uniformity serves to help the ol' Mark-IV eyeball make sense out of the log entries. To illustrate, shown below is the log-file layout that tries to maintain a certain place for each logging element and where the message body is limited to say- - - 80-bytes.
.... ....1.... ....2.... ....3.... ....4.... ....5.... ....6.... ....7.... ....8
2007-08-24 13:39:06 [DEBUG] Implementation title : log4j at line [ 62] of class [Log4JTest.run()] [Logger:log4j.test.Log4JTest]
2007-08-24 13:39:06 [DEBUG] Implementation vendor : "Apache Software Foundation" at line [ 63] of class [Log4JTest.run()] [Logger:log4j.test.Log4JTest]
2007-08-24 13:39:06 [DEBUG] Implementation version : 1.2.14 at line [ 64] of class [Log4JTest.run()] [Logger:log4j.test.Log4JTest]
But what about when the message body is more than 80-bytes? That would hork-up the "pretty factor", huh? Now it was apparent that when a message body is more than 80 bytes, it should be wrapped to the next line, BUT, maintain the same information about where the logging event (line number, mainly) took place AND the developer should not have to deal with that issue. All the developer should do is what they were used to doing in the first place... mainly this:
log.info(theReallyLongMessage);
The whole solution
Extend the ConsoleAppender class in log4j. The idea being pursued here is to intercept the message, testing the message body length and if greater than 80-bytes, breaking up the message body into 80-byte segments and reconstituting the message segment into a new LoggingEvent object. Simple, really.
/*
* History of modification
*
* History of modification
*
* Date Project By Description
* -------- ----------- ----------- -----------------------------------------------
*
*
*/
package com.log4j.extensions;
/**
* This class provides the means for wrapping an arbitrary logging event-line, so
* that the body of the message - this is, "%m" - is wrapped to the next line(s).
*
* Keep in mind we aren't interested in formatting or altering the pattern layout
* as the logging event has already been formatted. The only aspect this class
* deals with is the actual length of the message; if it's greater than 80 bytes,
* the message will be broken up into 80-byte segments and sent to the ConsoleAppender.
*
*
*
*/
import java.util.ArrayList;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.Layout;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.spi.LoggingEvent;
public class FoldingConsoleAppender extends ConsoleAppender {
private final static int LINE_LIMIT = 80;
private final static String HEADER = "THE FOLLOWING LINES WERE SPLIT INTO " +
LINE_LIMIT + " BYTE SEGMENTS TO FIT INTO THIS SPACE.";
private final static String FOOTER = "FOLDING COMPLETED";
private final static String NULL_MESSAGE = "The initial message value of null has been replaced with this message.";
/**
*
* Overrides the parent class method 'subAppend()' so that the Logging event can
* be intercepted to inspect the lenght of the message (%m).
*
*/
protected void subAppend(LoggingEvent event) {
//Take the logging event and inspect the message. If greater than 80-bytes
//create a CustomLoggingEvent with the 80-byte portion and send to the
//parent class subAppend() method. Repeat for the succeeding lines required
//to complete the process.
if ((String)event.getMessage() == null) {
String catClass = event.fqnOfCategoryClass;
Logger logger = Logger.getLogger(event.getLoggerName());
long ts = event.timeStamp;
Level level = event.getLevel();
Object message = NULL_MESSAGE;
LoggingEvent cle = new LoggingEvent(catClass, logger, ts, level, message, null);
super.subAppend(cle);
return;
}
if (((String)event.getMessage()).length() > LINE_LIMIT) {
Object m = event.getMessage();
String line = (String) event.getMessage();
if (line.length() > LINE_LIMIT) {
//Call foldLine to break up the message into 80-byte segments which
//have been placed into the array list.
ArrayList al = this.foldLine(line, LINE_LIMIT);
//For every 80-byte segment in the ArrayList, create a CustomLoggingEvent
//object using information from the original LoggingEvent but put the
//80-byte line into the new CustomLoggingEvent
for (int i = 0; i < al.size(); i++) {
Object o = al.get(i);
String catClass = event.fqnOfCategoryClass;
Logger logger = Logger.getLogger(event.getLoggerName());
long ts = event.timeStamp;
Level level = event.getLevel();
Object message = al.get(i);
LoggingEvent cle = new LoggingEvent(catClass, logger, ts, level, message, null);
super.subAppend(cle);
}
}
} else {
//Prints any log entry <= LINE_LIMIT bytes in length
super.subAppend(event);
}
}
/**
*
* Take the message body (%m) and divide it into 80-byte line-segments placing
* each segment (with preseding and ending note) into an ArrayList to be returned.
*
* @param line
* @param maxLen
* @return
*/
public ArrayList foldLine(String line, int maxLen) {
String foldedLine = null;
StringBuffer sb = new StringBuffer();
ArrayList al = new ArrayList();
int index = 0;
int lineLength = line.length();
int endIndex = index + maxLen;
al.add(HEADER);
while (index < lineLength) {
al.add(line.substring(index, endIndex));
index = index + maxLen;
endIndex = index + maxLen;
if (endIndex > lineLength) {
endIndex = lineLength;
}
}
al.add(FOOTER);
foldedLine = sb.toString();
return al;
}
}
Now that the class is created, place it into any package of your project. Next, modify your log4j.xml segment that describes the use of the ConsoleAppender as shown below.

Now, whenever the developer logs a message longer than 80-bytes:
//This demonstrates the idea of "folding" a line to ensure it fits inside the
//message space defined in the layout ( %-80m ) of the appender
String line = ".... ....1.... ....2.... ....3.... ....4.... ....5.... ....6.... ....7.... ....8.... ....9.... ....0";
log.info(line);
Instead of this message formatting .... (some messages appear before and after)
2007-08-24 10:26:19 [ERROR] 3. ERROR error message at line [ 39] of class [ArbitraryAppenderReference.log()] [Logger:Example_Logger]
2007-08-24 10:26:19 [FATAL] 4. FATAL error message at line [ 40] of class [ArbitraryAppenderReference.log()] [Logger:Example_Logger]
2007-08-24 10:26:19 [INFO ] .... ....1.... ....2.... ....3.... ....4.... ....5.... ....6.... ....7.... ....8.... ....9.... ....0 at line [ 43] of class [ArbitraryAppenderReference.log()] [Logger:Example_Logger]
2007-08-24 10:28:56 [INFO ] This logging entry is attached to appender 'Example_Logger'. at line [ 21] of class [ArbitraryAppenderReference.log()] [Logger:Example_Logger]
2007-08-24 10:28:56 [INFO ] and originated from class ArbitraryAppenderReference at line [ 22] of class [ArbitraryAppenderReference.log()] [Logger:Example_Logger]
... the message is formatted like this.
2007-08-24 10:26:19 [ERROR] 3. ERROR error message at line [ 39] of class [ArbitraryAppenderReference.log()] [Logger:Example_Logger]
2007-08-24 10:26:19 [FATAL] 4. FATAL error message at line [ 40] of class [ArbitraryAppenderReference.log()] [Logger:Example_Logger]
2007-08-24 13:39:06 [INFO ] THE FOLLOWING LINES WERE SPLIT INTO 80 BYTE SEGMENTS TO FIT INTO THIS SPACE. at line [ 33] of class [Log4JTest.run()] [Logger:log4j.test.Log4JTest]
2007-08-24 13:39:06 [INFO ] .... ....1.... ....2.... ....3.... ....4.... ....5.... ....6.... ....7.... ....8 at line [ 33] of class [Log4JTest.run()] [Logger:log4j.test.Log4JTest]
2007-08-24 13:39:06 [INFO ] .... ....9.... ....0 at line [ 33] of class [Log4JTest.run()] [Logger:log4j.test.Log4JTest]
2007-08-24 13:39:06 [INFO ] FOLDING COMPLETED at line [ 33] of class [Log4JTest.run()] [Logger:log4j.test.Log4JTest]
2007-08-24 10:28:56 [INFO ] This logging entry is attached to appender 'Example_Logger'. at line [ 21] of class [ArbitraryAppenderReference.log()] [Logger:Example_Logger]
2007-08-24 10:28:56 [INFO ] and originated from class ArbitraryAppenderReference at line [ 22] of class [ArbitraryAppenderReference.log()] [Logger:Example_Logger]
You can take the same FoldingConsoleAppender class above and use it as the basis to extend Log4J's FileAppender and DailyRollingFileAppender.
Thanks to Log4J's open source position !
Monday, August 20, 2007
Log4J all over again
I've not had to do Log4J configuration since before the time of the introduction of log4j.xml. Before that I used the simple log4j.properties variation.
But now, since I've run across an application using lo4j.xml I've had to learn how to configure Log4J using it. Initially, I thought "Argh" but as it turns out, it's not as bad as I had feared.
Having searched high and low on the internet, I found a lot of sites on the subject on log4j configuration. Some sites were good and other sites were not so good. All of them were broad; explained everything and then some. None offered real clarity. This was probably due to my impatience from just wanting a 1, 2, 3... list.
The log4j.xml file
Briefly, there's a layout to log4j.xml, shown below.

1. The class file
This is where the sequence of events begin. Somewhere, usually at the class level or perhaps in the constructor, you're going to have an entry such as this:
The name may appear to be arbitrary, but its actual value is important. This is because it is linked to a named logger defined in log4j.xml. Many examples show the getLogger() method like this:
Documentation such as this is a dis-service to both you and Log4j. The fact is that when getting a logger using the above approach returns the named logger only if one has been defined! If a logger having this name is not defined in log4j.xml a new logger instance will be created. Consequently, log entries will be sent to ALL log-files in your application. If you only have one then the problem is masked. Needless to say this approach should be avoided. Stick with names that are associated with named loggers you have defined in log4j.xml and your logging output to the correct log files will be much more predictable. Under this approach, you should expect to see something like this throughout your application:
Using named loggers you can create a separate set of log-files designed to contain entries for a given process.
2. Log4J looks-up the Logger Definition
Using the name ("Example_Logger") Log4J looks up logger information that were described using Logger Definitions.

From this lookup, Log4J obtains the name of the Appender.
3. Appender Definitions
Logger Definitions "point-to" Appenders which are described using Appender Definitions. Among other things, Appender Definitions define the log-file (and the filtering logging level) used to receive entries.

4. The Root Definition
The Root definition is used to define which logger is to descend from Log4J's root logger.
But now, since I've run across an application using lo4j.xml I've had to learn how to configure Log4J using it. Initially, I thought "Argh" but as it turns out, it's not as bad as I had feared.
Having searched high and low on the internet, I found a lot of sites on the subject on log4j configuration. Some sites were good and other sites were not so good. All of them were broad; explained everything and then some. None offered real clarity. This was probably due to my impatience from just wanting a 1, 2, 3... list.
The log4j.xml file
Briefly, there's a layout to log4j.xml, shown below.

1. The class file
This is where the sequence of events begin. Somewhere, usually at the class level or perhaps in the constructor, you're going to have an entry such as this:
Logger log = Logger.getLogger(name);
The name may appear to be arbitrary, but its actual value is important. This is because it is linked to a named logger defined in log4j.xml. Many examples show the getLogger() method like this:
Logger log = Logger.getLogger(this.getClass().getName() );
Documentation such as this is a dis-service to both you and Log4j. The fact is that when getting a logger using the above approach returns the named logger only if one has been defined! If a logger having this name is not defined in log4j.xml a new logger instance will be created. Consequently, log entries will be sent to ALL log-files in your application. If you only have one then the problem is masked. Needless to say this approach should be avoided. Stick with names that are associated with named loggers you have defined in log4j.xml and your logging output to the correct log files will be much more predictable. Under this approach, you should expect to see something like this throughout your application:
Logger log = Logger.getLogger("Example_Logger");
Using named loggers you can create a separate set of log-files designed to contain entries for a given process.
2. Log4J looks-up the Logger Definition
Using the name ("Example_Logger") Log4J looks up logger information that were described using Logger Definitions.

From this lookup, Log4J obtains the name of the Appender.
3. Appender Definitions
Logger Definitions "point-to" Appenders which are described using Appender Definitions. Among other things, Appender Definitions define the log-file (and the filtering logging level) used to receive entries.

4. The Root Definition
The Root definition is used to define which logger is to descend from Log4J's root logger.

Friday, July 13, 2007
Initialization on Demand Holder (IODH) Idiom Singleton
I read this short article about the controversial Singleton Pattern that explains the popular form of the pattern which is seen in so many Java applicaitons is basically flawed.
The conclusion of the article illustrates another approach using an inner class which is guaranteed to execute only once ensuring there is only one instance created (and is thread-safe).
The Java Language Specification (JLS) guarantees the object "instance" would not be initialised until someone calls getInstance() method.
Click to read article
Wiki on Initialization on Demand Holder pattern
The conclusion of the article illustrates another approach using an inner class which is guaranteed to execute only once ensuring there is only one instance created (and is thread-safe).
The Java Language Specification (JLS) guarantees the object "instance" would not be initialised until someone calls getInstance() method.
public class Singleton {
static class SingletonHolder {
static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
Click to read article
Wiki on Initialization on Demand Holder pattern
Tuesday, June 12, 2007
Eclipse Tip - Showing project path in title bar
To show the project's path in Eclipse's title bar, incorporate the '-showlocation' parameter when starting Eclipse. Right-click on the Eclipse icon to expose the properties and add it to the end of the start-command.
C:\Eclipse322\eclipse\eclipse.exe -showlocation
Wednesday, May 30, 2007
XStream
I haven't tried this one yet, but it could prove useful so I'm sticking it here.
XStream provides the means of serializing objects into XML and back again.
Read more here
XStream provides the means of serializing objects into XML and back again.
Read more here
Saturday, May 26, 2007
Thursday, May 24, 2007
Declaring constants
Java transgression #3
Do not use interfaces to act as the container for constants. Interfaces should only be used to define types. Using an interface causes internal detail, such as constants, to leak into the class’s public API. Once something becomes part of the public API you can never get rid of it.
Consider an abstract class with public static final constants. Using an abstract class is an implementation of the “uses” association. With the use of an abstract class, the intent is clear and design abuse such as when using interfaces cannot propagate to other classes.
Resource
Do not use interfaces to act as the container for constants. Interfaces should only be used to define types. Using an interface causes internal detail, such as constants, to leak into the class’s public API. Once something becomes part of the public API you can never get rid of it.
Consider an abstract class with public static final constants. Using an abstract class is an implementation of the “uses” association. With the use of an abstract class, the intent is clear and design abuse such as when using interfaces cannot propagate to other classes.
Resource
Configuration file abuse
Java transgression #2
For values that are deemed worthy of being placed into a “configuration file” e.g. config.xml or config.properties consider the nature and use of such a “configurable” value.
In a business world, when a value in the so-called config-file needs changing an application re-build and re-deploy is likely required. Therefore, it becomes no more advantageous than a class-level constant and defeats the implied intent of the value residing in the configuration file.
Consider using a database in concert with an administration tool that allows for values to be changed on-the-fly. This would reduce time and resources and improve “time-to-market”. In addition, such an approach would have the potential to prevent invalid values from being entered and thus inadvertently causing application failure.
For values that are deemed worthy of being placed into a “configuration file” e.g. config.xml or config.properties consider the nature and use of such a “configurable” value.
In a business world, when a value in the so-called config-file needs changing an application re-build and re-deploy is likely required. Therefore, it becomes no more advantageous than a class-level constant and defeats the implied intent of the value residing in the configuration file.
Consider using a database in concert with an administration tool that allows for values to be changed on-the-fly. This would reduce time and resources and improve “time-to-market”. In addition, such an approach would have the potential to prevent invalid values from being entered and thus inadvertently causing application failure.
Avoid the declaration of anonymous inner classes
Java Transgression #1
Action listener constructs associated with JButton, JTable, etc., (objects commonly seen within Swing applications) are frequently seen employing anonymous inner classes. If using Eclipse's VE and adding action listeners, it's hard to avoid as it places them in your application to provide function for the related component.
Refactoring results in the creation of a listener class, instantiating an object based upon that class and then passing it as the argument for the event listener.
Extending the listener class and assigning it to the component results in the elimination of inner classes altogether. The application will even load faster because the JVM doesn't spend time loading those inner classes.
In addition, employing anonymous inner classes kills any hope for object reuse.
Action listener constructs associated with JButton, JTable, etc., (objects commonly seen within Swing applications) are frequently seen employing anonymous inner classes. If using Eclipse's VE and adding action listeners, it's hard to avoid as it places them in your application to provide function for the related component.
Refactoring results in the creation of a listener class, instantiating an object based upon that class and then passing it as the argument for the event listener.
Extending the listener class and assigning it to the component results in the elimination of inner classes altogether. The application will even load faster because the JVM doesn't spend time loading those inner classes.
In addition, employing anonymous inner classes kills any hope for object reuse.
Wednesday, May 23, 2007
Maven 2.0.6 Installation and proxy configuration
I ran into a very annoying situation while installing Maven 2.0.6 where the document I was following suggested running the following command as part of its quasi tutorial chapter:
After running it I was greeted with this jewel :
Never mind the part of the message suggesting to re-run using the -e switch. The suggestion to do so is really another waste of time offering no new insight as to the real nature of the error. (Typical of error messages, right?)
The appropriate error message required here for me would actually need to read like this:
"Hey, stupid! Maven is a build tool requiring an unfettered internet connection so that it can obtain resource files on an 'as needed' basis, sort of like a Just In Time (JIT) inventory system where parts are ordered as needed. What this means (in spite of what you may have read in the docs) is that you need to copy the settings.xml into your system's home directory.
That in turn means your settings.xml file should be located inside the ~/.m2/directory, where ~ is the current user's home. So on Windows that directory path should resemble c:\documents and settings\\ and on UNIX/Linux it's /home/."
While trying hard (and failing) to stay as far away from the topic of meaningful error messages, (without the insults.. ok, maybe benign ones here and there.) there's still a bit more information required here.
The settings.xml file that comes with Maven is in itself ineffective. This is because 99% of it is commented-out. So rather than deal with a large file, use this version for starters and change to match your environment.

First, notice the reference to the local repository. Yep, Maven needs that location. That's where it's going to place those things the absence of which prevent Maven from working. You can see it below and see the newly added contents created by Maven when I finally fixed this problem.

Second are the entries describing the proxy server name and its port. If you can't determine the name of the proxy server you will need to talk with your company's LAN personnel. Of note is the fact that user id and password are not required as hinted at by the related entries in the ship-with version of settings.xml. In my case, I didn't need them.
With this done, and relying on the fact that you have no other issues falling outside the scope of this post, when you rerun the "mvn archetype:create ... " command you should now be greeted with a list like the one I got, below.
Now go visit your Maven repository and note the addition of new directories.
In addition, you should see a new project directory resembling this one.
mvn archetype:create
-DarchetypeGroupId=org.apache.maven.archetypes
-DgroupId=com.mycompany.app
-DartifactId=my-app
After running it I was greeted with this jewel :
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'archetype'.
[INFO] ----------------------------------------------------------------------------
[ERROR] BUILD ERROR
[INFO] ----------------------------------------------------------------------------
[INFO] The plugin 'org.apache.maven.plugins:maven-archetype-plugin' does not exist
or no valid version could be found
[INFO] ----------------------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] For more information, run Maven with the -e switch
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1 second
[INFO] Finished at: Wed May 23 12:11:05 CDT 2007
[INFO] Final Memory: 4M/8M
[INFO] ------------------------------------------------------------------------
Never mind the part of the message suggesting to re-run using the -e switch. The suggestion to do so is really another waste of time offering no new insight as to the real nature of the error. (Typical of error messages, right?)
The appropriate error message required here for me would actually need to read like this:
"Hey, stupid! Maven is a build tool requiring an unfettered internet connection so that it can obtain resource files on an 'as needed' basis, sort of like a Just In Time (JIT) inventory system where parts are ordered as needed. What this means (in spite of what you may have read in the docs) is that you need to copy the settings.xml into your system's home directory.
That in turn means your settings.xml file should be located inside the ~/.m2/directory, where ~ is the current user's home. So on Windows that directory path should resemble c:\documents and settings\
While trying hard (and failing) to stay as far away from the topic of meaningful error messages, (without the insults.. ok, maybe benign ones here and there.) there's still a bit more information required here.
The settings.xml file that comes with Maven is in itself ineffective. This is because 99% of it is commented-out. So rather than deal with a large file, use this version for starters and change to match your environment.

First, notice the reference to the local repository. Yep, Maven needs that location. That's where it's going to place those things the absence of which prevent Maven from working. You can see it below and see the newly added contents created by Maven when I finally fixed this problem.

Second are the entries describing the proxy server name and its port. If you can't determine the name of the proxy server you will need to talk with your company's LAN personnel. Of note is the fact that user id and password are not required as hinted at by the related entries in the ship-with version of settings.xml. In my case, I didn't need them.
With this done, and relying on the fact that you have no other issues falling outside the scope of this post, when you rerun the "mvn archetype:create ... " command you should now be greeted with a list like the one I got, below.
C:\>mvn archetype:create -DarchetypeGroupId=org.apache.maven.archetypes -DgroupId=com.mycompany.app -DartifactId=my-app
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'archetype'.
[INFO] org.apache.maven.plugins: checking for updates from central
[INFO] org.codehaus.mojo: checking for updates from central
[INFO] artifact org.apache.maven.plugins:maven-archetype-plugin: checking for updates from central
Downloading: http://repo1.maven.org/maven2/org/apache/maven/plugins/maven-archetype-plugin/1.0-alpha-4/maven-archetype-plugin-1.0-alpha-4.pom
1K downloaded
Downloading: http://repo1.maven.org/maven2/org/apache/maven/archetype/maven-archetype/1.0-alpha-4/maven-archetype-1.0-alpha-4.pom
2K downloaded
Downloading: http://repo1.maven.org/maven2/org/apache/maven/maven-parent/1/maven-parent-1.pom
6K downloaded
Downloading: http://repo1.maven.org/maven2/org/apache/apache/1/apache-1.pom
3K downloaded
Downloading: http://repo1.maven.org/maven2/org/apache/maven/plugins/maven-archetype-plugin/1.0-alpha-4/maven-archetype-plugin-1.0-alpha-4.jar
9K downloaded
[INFO] ----------------------------------------------------------------------------
[INFO] Building Maven Default Project
[INFO] task-segment: [archetype:create] (aggregator-style)
[INFO] ----------------------------------------------------------------------------
Downloading: http://repo1.maven.org/maven2/org/apache/maven/archetype/maven-archetype-core/1.0-alpha-4/maven-archetype-core-1.0-alpha-4.pom
1K downloaded
Downloading: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus-velocity/1.1.2/plexus-velocity-1.1.2.pom
7K downloaded
Downloading: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus-container-default/1.0-alpha-7/plexus-container-default-1.0-alpha-7.pom
1K downloaded
Downloading: http://repo1.maven.org/maven2/plexus/plexus-containers/1.0.2/plexus-containers-1.0.2.pom
471b downloaded
Downloading: http://repo1.maven.org/maven2/plexus/plexus-root/1.0.3/plexus-root-1.0.3.pom
5K downloaded
Downloading: http://repo1.maven.org/maven2/junit/junit/3.8.1/junit-3.8.1.pom
145b downloaded
Downloading: http://repo1.maven.org/maven2/plexus/plexus-utils/1.0.2/plexus-utils-1.0.2.pom
740b downloaded
Downloading: http://repo1.maven.org/maven2/classworlds/classworlds/1.1-alpha-2/classworlds-1.1-alpha-2.pom
3K downloaded
Downloading: http://repo1.maven.org/maven2/commons-collections/commons-collections/2.0/commons-collections-2.0.pom
171b downloaded
Downloading: http://repo1.maven.org/maven2/commons-logging/commons-logging-api/1.0.4/commons-logging-api-1.0.4.pom
168b downloaded
Downloading: http://repo1.maven.org/maven2/velocity/velocity/1.4/velocity-1.4.pom
2K downloaded
Downloading: http://repo1.maven.org/maven2/velocity/velocity-dep/1.4/velocity-dep-1.4.pom
1K downloaded
Downloading: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus-utils/1.1/plexus-utils-1.1.pom
767b downloaded
Downloading: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus/1.0.4/plexus-1.0.4.pom
5K downloaded
Downloading: http://repo1.maven.org/maven2/org/apache/maven/maven-model/2.0/maven-model-2.0.pom
2K downloaded
Downloading: http://repo1.maven.org/maven2/org/apache/maven/maven/2.0/maven-2.0.pom
8K downloaded
Downloading: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus-utils/1.0.4/plexus-utils-1.0.4.pom
6K downloaded
Downloading: http://repo1.maven.org/maven2/org/apache/maven/maven-artifact-manager/2.0/maven-artifact-manager-2.0.pom
1K downloaded
Downloading: http://repo1.maven.org/maven2/org/apache/maven/maven-repository-metadata/2.0/maven-repository-metadata-2.0.pom
1K downloaded
Downloading: http://repo1.maven.org/maven2/org/apache/maven/maven-artifact/2.0/maven-artifact-2.0.pom
723b downloaded
Downloading: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus-container-default/1.0-alpha-8/plexus-container-default-1.0-alpha-8.pom
7K downloaded
Downloading: http://repo1.maven.org/maven2/org/apache/maven/wagon/wagon-provider-api/1.0-alpha-5/wagon-provider-api-1.0-alpha-5.pom
4K downloaded
Downloading: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus-container-default/1.0-alpha-9/plexus-container-default-1.0-alpha-9.pom
1K downloaded
Downloading: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus-containers/1.0.3/plexus-containers-1.0.3.pom
492b downloaded
Downloading: http://repo1.maven.org/maven2/dom4j/dom4j/1.6.1/dom4j-1.6.1.pom
6K downloaded
Downloading: http://repo1.maven.org/maven2/xml-apis/xml-apis/1.0.b2/xml-apis-1.0.b2.pom
365b downloaded
Downloading: http://repo1.maven.org/maven2/org/apache/maven/archetype/maven-archetype-creator/1.0-alpha-4/maven-archetype-creator-1.0-alpha-4.pom
1K downloaded
Downloading: http://repo1.maven.org/maven2/org/apache/maven/archetype/maven-archetype-model/1.0-alpha-4/maven-archetype-model-1.0-alpha-4.pom
1K downloaded
Downloading: http://repo1.maven.org/maven2/org/apache/maven/maven-project/2.0/maven-project-2.0.pom
1K downloaded
Downloading: http://repo1.maven.org/maven2/org/apache/maven/maven-profile/2.0/maven-profile-2.0.pom
1K downloaded
Downloading: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus-archiver/1.0-alpha-5/plexus-archiver-1.0-alpha-5.pom
439b downloaded
Downloading: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus-components/1.1.4/plexus-components-1.1.4.pom
2K downloaded
Downloading: http://repo1.maven.org/maven2/oro/oro/2.0.8/oro-2.0.8.pom
140b downloaded
Downloading: http://repo1.maven.org/maven2/org/apache/maven/maven-plugin-api/2.0/maven-plugin-api-2.0.pom
601b downloaded
Downloading: http://repo1.maven.org/maven2/plexus/plexus-utils/1.0.2/plexus-utils-1.0.2.jar
156K downloaded
Downloading: http://repo1.maven.org/maven2/org/apache/maven/archetype/maven-archetype-creator/1.0-alpha-4/maven-archetype-creator-1.0-alpha-4.jar
21K downloaded
Downloading: http://repo1.maven.org/maven2/dom4j/dom4j/1.6.1/dom4j-1.6.1.jar
306K downloaded
Downloading: http://repo1.maven.org/maven2/commons-logging/commons-logging-api/1.0.4/commons-logging-api-1.0.4.jar
25K downloaded
Downloading: http://repo1.maven.org/maven2/xml-apis/xml-apis/1.0.b2/xml-apis-1.0.b2.jar
106K downloaded
Downloading: http://repo1.maven.org/maven2/oro/oro/2.0.8/oro-2.0.8.jar
63K downloaded
Downloading: http://repo1.maven.org/maven2/velocity/velocity-dep/1.4/velocity-dep-1.4.jar
505K downloaded
Downloading: http://repo1.maven.org/maven2/org/apache/maven/archetype/maven-archetype-core/1.0-alpha-4/maven-archetype-core-1.0-alpha-4.jar
22K downloaded
Downloading: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus-archiver/1.0-alpha-5/plexus-archiver-1.0-alpha-5.jar
129K downloaded
Downloading: http://repo1.maven.org/maven2/velocity/velocity/1.4/velocity-1.4.jar
352K downloaded
Downloading: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus-velocity/1.1.2/plexus-velocity-1.1.2.jar
7K downloaded
Downloading: http://repo1.maven.org/maven2/org/apache/maven/archetype/maven-archetype-model/1.0-alpha-4/maven-archetype-model-1.0-alpha-4.jar
15K downloaded
Downloading: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus-utils/1.1/plexus-utils-1.1.jar
164K downloaded
Downloading: http://repo1.maven.org/maven2/commons-collections/commons-collections/2.0/commons-collections-2.0.jar
88K downloaded
[INFO] Setting property: classpath.resource.loader.class => 'org.codehaus.plexus.velocity.ContextClassLoaderResourceLoader'.
[INFO] Setting property: velocimacro.messages.on => 'false'.
[INFO] Setting property: resource.loader => 'classpath'.
[INFO] Setting property: resource.manager.logwhenfound => 'false'.
[INFO] **************************************************************
[INFO] Starting Jakarta Velocity v1.4
[INFO] RuntimeInstance initializing.
[INFO] Default Properties File: org\apache\velocity\runtime\defaults\velocity.properties
[INFO] Default ResourceManager initializing. (class org.apache.velocity.runtime.resource.ResourceManagerImpl)
[INFO] Resource Loader Instantiated: org.codehaus.plexus.velocity.ContextClassLoaderResourceLoader
[INFO] ClasspathResourceLoader : initialization starting.
[INFO] ClasspathResourceLoader : initialization complete.
[INFO] ResourceCache : initialized. (class org.apache.velocity.runtime.resource.ResourceCacheImpl)
[INFO] Default ResourceManager initialization complete.
[INFO] Loaded System Directive: org.apache.velocity.runtime.directive.Literal
[INFO] Loaded System Directive: org.apache.velocity.runtime.directive.Macro
[INFO] Loaded System Directive: org.apache.velocity.runtime.directive.Parse
[INFO] Loaded System Directive: org.apache.velocity.runtime.directive.Include
[INFO] Loaded System Directive: org.apache.velocity.runtime.directive.Foreach
[INFO] Created: 20 parsers.
[INFO] Velocimacro : initialization starting.
[INFO] Velocimacro : adding VMs from VM library template : VM_global_library.vm
[ERROR] ResourceManager : unable to find resource 'VM_global_library.vm' in any resource loader.
[INFO] Velocimacro : error using VM library template VM_global_library.vm : org.apache.velocity.exception.ResourceNotFoundException: Unable to find resource 'VM_global_library.vm'
[INFO] Velocimacro : VM library template macro registration complete.
[INFO] Velocimacro : allowInline = true : VMs can be defined inline in templates
[INFO] Velocimacro : allowInlineToOverride = false : VMs defined inline may NOT replace previous VM definitions
[INFO] Velocimacro : allowInlineLocal = false : VMs defined inline will be global in scope if allowed.
[INFO] Velocimacro : initialization complete.
[INFO] Velocity successfully started.
[INFO] [archetype:create]
[INFO] Defaulting package to group ID: com.mycompany.app
[INFO] artifact org.apache.maven.archetypes:maven-archetype-quickstart: checking for updates from central
Downloading: http://repo1.maven.org/maven2/org/apache/maven/archetypes/maven-archetype-quickstart/1.0/maven-archetype-quickstart-1.0.jar
4K downloaded
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating Archetype: maven-archetype-quickstart:RELEASE
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: com.mycompany.app
[INFO] Parameter: packageName, Value: com.mycompany.app
[INFO] Parameter: basedir, Value: C:\
[INFO] Parameter: package, Value: com.mycompany.app
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: artifactId, Value: my-app
[INFO] ********************* End of debug info from resources from generated POM ***********************
[INFO] Archetype created in dir: C:\my-app
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 10 seconds
[INFO] Finished at: Wed May 23 11:38:57 CDT 2007
[INFO] Final Memory: 4M/8M
[INFO] ------------------------------------------------------------------------
Now go visit your Maven repository and note the addition of new directories.
In addition, you should see a new project directory resembling this one.

Saturday, May 19, 2007
Application properties and storage strategies
It baffles and disappoints
I've been in this industry for a long time. I've seen the good and bad and take away from each experience lessons for the next project. It's not uncommon to look back on the way an application came together and think about how it could have been done differently to improve maintainability, robustness, etc.
Now in the Java world and enjoying it thoroughly, I'm continuously exposed to an aspect of Java development that both baffles and disappoints; property files.
What in the hell were “they” thinking?
The idea being employed by property files is to have a place to store application related information such as database connection criteria, behavioral specifications, etc. The property file is nothing but a common text file. I know of an inter-net application that relies upon a property file named “system.properties” which contains names of other property files to be processed during the startup phase!
Although this approach to storing properties for use by applications is common to see, it is problematic. As stated, these files are nothing more than text files. To change their contents, various text editors or even word processors are used. By its nature, information entered is prone to error and it is easy for anyone with access to the machine to change the contents of these files. In addition, there is no audit capability.
Usually, these property files are given names to reflect their role within the system. Sometimes, due to system changes giving rise for the need to create new property entries or remove old ones, the name no longer reflects those contents. Other times, the developer just finds it easier to add new property entries rather than create a new property file to contain those entries. If lucky, a suitable property file is found and the property values are added. If not so lucky, the developer sticks the properties into any file that exists.
It gets unwieldy quickly. Take for example a certain inter-net based order entry system used by a distribution company. Because of the user-volume, traffic is balanced across three application servers connected to a common database server. This approach calls for three separate sets of property files with each set residing in the three application servers. Most of the data within the property files is identical but some of the properties reflect the name of the application server itself while other properties relate to data queue names allocated for that server. Due to design requirements there are many property files containing over 2,300 entries. Together, that architecture calls for the existence of over 7,000 properties.
There are better ways to address this design requirement of providing static or server specific data to an application.
A properties file editor
This stand-alone editor provides the means to establish connection criteria to the database used by the client application. The data that can be maintained is listed in the table below.


The above is an example of an application designed to store values to be used by one or several other applications to govern database connection criteria. In addition it provides the means to establish other values. Such data could be the name or address and the contact information of the company that might be accessed by a billing application. You get the idea; static data.
The application stores the data entered into a Java serialized object. This provides a level of security in that the data within cannot be easily edited; you have to use the application to do that.
Below is a diagram that illustrates how the application stores the information entered.


Even though the above is by far a better approach compared to using a text file to store application property data, there is even a better method to storing static data such as state abbreviations, company addresses, application security contexts, etc., etc. ....
USE A DATABASE !!!
I've been in this industry for a long time. I've seen the good and bad and take away from each experience lessons for the next project. It's not uncommon to look back on the way an application came together and think about how it could have been done differently to improve maintainability, robustness, etc.
Now in the Java world and enjoying it thoroughly, I'm continuously exposed to an aspect of Java development that both baffles and disappoints; property files.
What in the hell were “they” thinking?
The idea being employed by property files is to have a place to store application related information such as database connection criteria, behavioral specifications, etc. The property file is nothing but a common text file. I know of an inter-net application that relies upon a property file named “system.properties” which contains names of other property files to be processed during the startup phase!
#
# Property File name : dbConnection.properties
#
# DBTYPE.URL.NAME=VALUE
# DBTYPE : [mySQL, DB400]
# URL : Example localhost, 10.240.19.84, as240d
# Parm NAME : dbPooling [on, off]
# Parm NAME : dbPoolsize [1,2,3...] 0=off
# Parm NAME : dbName For mySQL, the DB Name. For iSeries, the library
# Parm NAME : dbUserID User ID having authorized access
# Parm NAME : dbPassword Password for the user having authorized access
#
# Syntax Examples:
# DB400.as240d.poolsize=5 defines a connection pool size of 5 for an AS400
# database located on the machine identified as as240d (IP add resolved via DNS).
#
# mySQL.as240d.dbPooling=off specifies that pooling of db connections is off. The
# application software is required to create a connection either by prompting or
# using the values specified here. Prompted values are treated as overrides.
#
mySQL.localhost.dbPooling=off
mySQL.localhost.dbPoolsize=0
mySQL.localhost.dbName=resources
mySQL.localhost.dbUserID=prbxf000
mySQL.localhost.dbPassword=prbxf000
DB400.as240d.dbPooling=on
DB400.as240d.dbPoolsize=3
DB400.as240d.dbName=scdbfp10
DB400.as240d.dbUserID=2br02b
DB400.as240d.dbPassword=07734
# end of property file
Although this approach to storing properties for use by applications is common to see, it is problematic. As stated, these files are nothing more than text files. To change their contents, various text editors or even word processors are used. By its nature, information entered is prone to error and it is easy for anyone with access to the machine to change the contents of these files. In addition, there is no audit capability.
Usually, these property files are given names to reflect their role within the system. Sometimes, due to system changes giving rise for the need to create new property entries or remove old ones, the name no longer reflects those contents. Other times, the developer just finds it easier to add new property entries rather than create a new property file to contain those entries. If lucky, a suitable property file is found and the property values are added. If not so lucky, the developer sticks the properties into any file that exists.
It gets unwieldy quickly. Take for example a certain inter-net based order entry system used by a distribution company. Because of the user-volume, traffic is balanced across three application servers connected to a common database server. This approach calls for three separate sets of property files with each set residing in the three application servers. Most of the data within the property files is identical but some of the properties reflect the name of the application server itself while other properties relate to data queue names allocated for that server. Due to design requirements there are many property files containing over 2,300 entries. Together, that architecture calls for the existence of over 7,000 properties.
There are better ways to address this design requirement of providing static or server specific data to an application.
A properties file editor
This stand-alone editor provides the means to establish connection criteria to the database used by the client application. The data that can be maintained is listed in the table below.


The above is an example of an application designed to store values to be used by one or several other applications to govern database connection criteria. In addition it provides the means to establish other values. Such data could be the name or address and the contact information of the company that might be accessed by a billing application. You get the idea; static data.
The application stores the data entered into a Java serialized object. This provides a level of security in that the data within cannot be easily edited; you have to use the application to do that.
Below is a diagram that illustrates how the application stores the information entered.


Even though the above is by far a better approach compared to using a text file to store application property data, there is even a better method to storing static data such as state abbreviations, company addresses, application security contexts, etc., etc. ....
USE A DATABASE !!!
Monday, May 14, 2007
HP Laptop, MS Vista Firewall and MySQL
I bought a laptop earlier this month. It's the HP I've eyed for awhile now. Although I'm happy with the choice I'm kicking myself for waiting too long and now have to deal with Microsoft Vista instead of Windows XP, which is what I wanted.
It came with Windows Vista Home Premium pre-installed of course, and the guy at the store described it as Windows XP with some entertainment features. Being statisfied (am I crazy or what?) with that explaination I was sold.
I installed MySQL almost immediately and ran into problems when using the Configuration Wizard during the post-install process. Seems as though SQL has no access to the DB.
As it turns out it was a firewall issue. Adding the appropriate program to the firewall's exlusion list solved the problem.
It came with Windows Vista Home Premium pre-installed of course, and the guy at the store described it as Windows XP with some entertainment features. Being statisfied (am I crazy or what?) with that explaination I was sold.
I installed MySQL almost immediately and ran into problems when using the Configuration Wizard during the post-install process. Seems as though SQL has no access to the DB.
As it turns out it was a firewall issue. Adding the appropriate program to the firewall's exlusion list solved the problem.
Thursday, May 10, 2007
Inadvertantly disabled user profile (iSeries)
Back in September of 2004, a colleague was developing a Swing application. I wasn't involved much at all but was consulted when problems arouse with my DBConnection class he incorporated into his project for connection management to the AS/400, err ... iSeries machine. The problem arose during the time the user logged into the iSeries database using the JDBC connection. It appeared the process was mysteriously rendering the user profile as expired. Some circles, including the one we consider ourselves to be in, view that behavior as a problem. It was a real mystery and left us scratching our heads.
Consulting IBM, we discovered the problem; it was the way the driver was being registered.
To me, it seems benign enough; it creates a new driver and then registers the driver object using the DriverManager object. However, IBM explained that using the DriverManager class actually registered the driver a second time resulting in two drivers in the driver manager's list.
As a consequence, when the user/developer was entering an invalid password for an unfortunate second time, it processed the event again with the second driver in the list and so counted that as the third invalid attempt. The user's profile had been established so that it became invalid on the third invalid login attempt.
The remedy was simple enough; comment out the 2nd line.
Consulting IBM, we discovered the problem; it was the way the driver was being registered.
Driver aDriver = (Driver)Class.forName(driver).newInstance();
DriverManager.registerDriver(aDriver);
To me, it seems benign enough; it creates a new driver and then registers the driver object using the DriverManager object. However, IBM explained that using the DriverManager class actually registered the driver a second time resulting in two drivers in the driver manager's list.
As a consequence, when the user/developer was entering an invalid password for an unfortunate second time, it processed the event again with the second driver in the list and so counted that as the third invalid attempt. The user's profile had been established so that it became invalid on the third invalid login attempt.
The remedy was simple enough; comment out the 2nd line.
Wednesday, May 9, 2007
A JVM Properties Viewer

This is mostly a bare-bones version of a basic Swing application that was created using Eclipse's visual editor. In it there is a single method to present the contents of the JVM properties into a text area.
See the method loadJVMData().
The whole application has been listed here so that you can cut-n-paste into your own editor and run it.
/*
* Modification History
*
* Date Project Pgmr Description
* -------- --------- ---- ---------------------------------------------------
* 02242004 Utilities BF Initial release
* 10072005 Utilities BF Refactored to use
* Properties properties = System.getProperties();
* to get entire list of properties.
*
*
*
*/
package com.utilities.ui;
import java.net.InetAddress;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
import java.util.Vector;
import javax.swing.JDialog;
import javax.swing.JFrame;
import com.utilities.general.StringRoutines;
/**
* @author
*
* To change the template for this generated type comment go to
* Window>Preferences>Java>Code Generation>Code and Comments
*/
public class JVMProperties extends JDialog {
private javax.swing.JPanel jContentPane = null;
private javax.swing.JPanel jPanel = null;
private javax.swing.JPanel jPanel1 = null;
private javax.swing.JPanel jPanel2 = null;
private javax.swing.JLabel jLabel = null;
private javax.swing.JButton jButton = null;
private javax.swing.JList jList = null;
private javax.swing.JScrollPane jScrollPane = null;
//private static JVMProperties jvmProps = null;
/**
* This is the most-always empty Main()
*/
public static void main(String[] args) {
JVMProperties jvmp = new JVMProperties(null,true);
jvmp.show();
System.exit(0);
}
/**
* This is the default constructor. It calls the process to perform
*/
public JVMProperties(JFrame parent, boolean modal) {
super(parent, modal);
initialize();
if (parent != null) {
this.setLocationRelativeTo(parent.getContentPane());
} else {
this.setLocationRelativeTo(null);
}
this.process();
}
/**
* This is the processor-controller
*/
public void process() {
this.loadJVMData();
}
/**
* This method was created in VisualAge.
*/
public void loadJVMData() {
String CR = System.getProperty("line.separator");
Vector vector = new Vector();
//Get the internet address of the machine in which this is running
try {
InetAddress inetAddr = InetAddress.getLocalHost();
String key = "Internet host name / address";
String nameAddr = inetAddr.getHostName() + "/" + inetAddr.getHostAddress();
int n = 30 - key.length();
if (n <= 0) n = 1;
String rightPad = StringRoutines.padStringLeft(">", n);
vector.add(key + rightPad + nameAddr);
} catch (Exception e) {
}
//Now get the rest of the proerties and add them to the list
Properties properties = System.getProperties();
Set set = properties.keySet();
Iterator setIter = set.iterator();
while(setIter.hasNext()) {
String key = (String)setIter.next();
String value = properties.getProperty(key);
int n = 30 - key.length();
if (n <= 0) n = 1;
String rightPad = StringRoutines.padStringLeft(">", n);
//System.out.println(key + rightEdge + value);
vector.add(key + rightPad + value);
}
//vector.add("java.naming.factory.initial>"+System.getProperty("java.naming.factory.initial"));
jList.setListData(vector);
//AppLog.debug(vector);
}
/**
* This method initializes this
*
* @return void
*/
private void initialize() {
this.setSize(604, 372);
this.setContentPane(getJContentPane());
this.setLocation(100, 200);
this.setTitle("Java Virtual Machine Properties Viewer");
}
/**
* This method initializes jContentPane
*
* @return javax.swing.JPanel
*/
private javax.swing.JPanel getJContentPane() {
if (jContentPane == null) {
jContentPane = new javax.swing.JPanel();
java.awt.GridBagConstraints consGridBagConstraints11 = new java.awt.GridBagConstraints();
java.awt.GridBagConstraints consGridBagConstraints10 = new java.awt.GridBagConstraints();
java.awt.GridBagConstraints consGridBagConstraints12 = new java.awt.GridBagConstraints();
consGridBagConstraints11.fill = java.awt.GridBagConstraints.BOTH;
consGridBagConstraints11.weighty = 200.0D;
consGridBagConstraints11.weightx = 1.0;
consGridBagConstraints11.gridx = 0;
consGridBagConstraints11.gridy = 1;
consGridBagConstraints10.fill = java.awt.GridBagConstraints.BOTH;
consGridBagConstraints10.weighty = 1.0;
consGridBagConstraints10.weightx = 1.0;
consGridBagConstraints10.gridy = 0;
consGridBagConstraints10.gridx = 0;
consGridBagConstraints12.fill = java.awt.GridBagConstraints.BOTH;
consGridBagConstraints12.weighty = 1.0;
consGridBagConstraints12.weightx = 1.0;
consGridBagConstraints12.gridx = 0;
consGridBagConstraints12.gridy = 2;
jContentPane.setLayout(new java.awt.GridBagLayout());
jContentPane.add(getJPanel(), consGridBagConstraints10);
jContentPane.add(getJPanel1(), consGridBagConstraints11);
jContentPane.add(getJPanel2(), consGridBagConstraints12);
}
return jContentPane;
}
/**
* This method initializes jPanel
*
* @return javax.swing.JPanel
*/
private javax.swing.JPanel getJPanel() {
if(jPanel == null) {
jPanel = new javax.swing.JPanel();
java.awt.FlowLayout layFlowLayout13 = new java.awt.FlowLayout();
layFlowLayout13.setAlignment(java.awt.FlowLayout.LEFT);
jPanel.setLayout(layFlowLayout13);
jPanel.add(getJLabel(), null);
}
return jPanel;
}
/**
* This method initializes jPanel1
*
* @return javax.swing.JPanel
*/
private javax.swing.JPanel getJPanel1() {
if(jPanel1 == null) {
jPanel1 = new javax.swing.JPanel();
jPanel1.setLayout(new java.awt.BorderLayout());
jPanel1.add(getJScrollPane(), java.awt.BorderLayout.CENTER);
jPanel1.setPreferredSize(new java.awt.Dimension(200,200));
}
return jPanel1;
}
/**
* This method initializes jPanel2
*
* @return javax.swing.JPanel
*/
private javax.swing.JPanel getJPanel2() {
if(jPanel2 == null) {
jPanel2 = new javax.swing.JPanel();
java.awt.FlowLayout layFlowLayout14 = new java.awt.FlowLayout();
layFlowLayout14.setAlignment(java.awt.FlowLayout.RIGHT);
jPanel2.setLayout(layFlowLayout14);
jPanel2.add(getJButton(), null);
}
return jPanel2;
}
/**
* This method initializes jLabel
*
* @return javax.swing.JLabel
*/
private javax.swing.JLabel getJLabel() {
if(jLabel == null) {
jLabel = new javax.swing.JLabel();
jLabel.setText("JVM Properties");
}
return jLabel;
}
/**
* This method initializes jButton
*
* @return javax.swing.JButton
*/
private javax.swing.JButton getJButton() {
if(jButton == null) {
jButton = new javax.swing.JButton();
jButton.setText("Close");
jButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent e) {
hide();
}
});
}
return jButton;
}
/**
* This method initializes jList
*
* @return javax.swing.JList
*/
private javax.swing.JList getJList() {
if(jList == null) {
jList = new javax.swing.JList();
jList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
jList.setPreferredSize(new java.awt.Dimension(800,800));
jList.setVisibleRowCount(5);
jList.setFont(new java.awt.Font("Courier New", java.awt.Font.PLAIN, 12));
}
return jList;
}
/**
* This method initializes jScrollPane
*
* @return javax.swing.JScrollPane
*/
private javax.swing.JScrollPane getJScrollPane() {
if(jScrollPane == null) {
jScrollPane = new javax.swing.JScrollPane();
jScrollPane.setViewportView(getJList());
jScrollPane.setPreferredSize(new java.awt.Dimension(274,300));
}
return jScrollPane;
}
} // @jve:visual-info decl-index=0 visual-constraint="10,10"
Tuesday, May 8, 2007
Eclipse Tip of March 31, 2006.
You can specify that during Eclipse’s startup phase Eclipse should provide a prompt to allow you to select from a list the project you want to work with. By default, this list is limited to showing the last 5 projects you used. Here, I will explain how to specify that the prompt should be shown and how to change the default setting which governs the number of projects in the list.
Showing the prompt.
Once Eclipse is presented, select the following menu path:
Window -> Preferences…
The Preferences Wizard will be presented. From the list provided on the left select:
General, Startup and Shutdown.
Check the option labeled as “Prompt for workspace on startup”. Click the “Apply” then the “Ok” button.
Now, end Eclipse. This is important because if Eclipse is left running you will not be able to edit the file being discussed in the next step.
Changing the default number of projects in the project select list.
Using Windows Explorer, locate the parent directory for your current Eclipse installation, then locate the “configuration” directory and finally the “.settings” directory. For example, my Eclipse instance (Version 3.1.0.) is installed in “Eclipse310”. So the path we want is
“C:\Eclipse310\eclipse\configuration\.settings”.
In the “.settings” directory there is a file named “org.eclipse.ui.ide.prefs”. Open it with your favorite text editor. Here are the contents:
#Fri Mar 31 07:34:04 CST 2006
RECENT_WORKSPACES_PROTOCOL=2
MAX_RECENT_WORKSPACES=10
SHOW_WORKSPACE_SELECTION_DIALOG=true
eclipse.preferences.version=1
RECENT_WORKSPACES=C\:\\Desk\\AppDevJava\\HibernateWorkspace,
C\:\\Desk\\AppDevJava\\DAOGenWorkspace,
C\:\\Desk\\AppDevJava\\TableTestWorkspace,
C\:\\Desk\\AppDevJava\\Development\\TableTests
The line we want to change is the one containing this “MAX_RECENT_WORKSPACES”.
As you can see, it has been changed from the default value of 5 to 10. Change the value for yours to oh, say 15 for example, and save.
Now, the next time you start Eclipse, you will be prompted to select from a list of 15 entries the project you wish to work with.
Friday, May 4, 2007
Reading Windows Environment Variables from Java
Environment variables can be simply described as user-defined name/value pairs. An example entry would take the form of "APP_ENV=dev".
For a complete overview of environment variables for MS Windows, refer to the following link: (http://support.microsoft.com/kb/310519) How To Manage Environment Variables in Windows XP.
In Java, you can send the complete contents of the environment variables to the console using the following method:
Running this method should produce a list similar to that shown below:
Environment variables are commonly used to control application behavior or to provide a resource location using a directory path. For developing Java applications, this can be a good way to control the program's behavior during development where certain features or processes should not be allowed to execute. This can be done by interrogating the value established by an environment variable such as "APP_ENV=dev".
You can also read a specific environment variable simply by providing the name of the variable using a method like this one:
For a complete overview of environment variables for MS Windows, refer to the following link: (http://support.microsoft.com/kb/310519) How To Manage Environment Variables in Windows XP.
In Java, you can send the complete contents of the environment variables to the console using the following method:
/**
*
*
*/
public static void dumpEnvironmentVariables() {
Map variables = System.getenv();
Set variableNames = variables.keySet();
Iterator nameIterator = variableNames.iterator();
System.out.println("===============================================================");
System.out.println("System Environment variables");
for (int index = 0; index < variableNames.size(); index++) {
String name = (String) nameIterator.next();
String value = (String) variables.get(name);
System.out.println(" " + name + "=" + value);
}
}
Running this method should produce a list similar to that shown below:
===============================================================
System Environment variables
CATALINA_HOME=C:\TomcatInstalls\apache-tomcat-5.5.20
HOMEDRIVE=C:
windir=C:\WINDOWS
SystemDrive=C:
ProgramFiles=C:\Program Files
APP_ENV=dev
.
.
.
Environment variables are commonly used to control application behavior or to provide a resource location using a directory path. For developing Java applications, this can be a good way to control the program's behavior during development where certain features or processes should not be allowed to execute. This can be done by interrogating the value established by an environment variable such as "APP_ENV=dev".
You can also read a specific environment variable simply by providing the name of the variable using a method like this one:
/**
* Accepts a string representing the key-name to a named environment variable.
* Environment variables for Windows can be established using the following steps:
*
* Start-> Settings-> System-> Advanced Tab-> Environment Variables button->
* Under System Variables, New button-> and add the name/value pair.
*
* @param name
* @return
*/
public static String getEnvVariable(String name) {
String value = null;
variables = System.getenv();
variableNames = variables.keySet();
return (String)variables.get(name);
}
Thursday, May 3, 2007
Swing based Font Properties Editor

Here's a utility to allow for quick selection of font properties showing the results in a phrase containing all letters of the alphabet. In addition, the developer can copy the resulting Java statement and paste it into their project code.
Rather than post the 500+ lines here, the code is available for the asking. Just post a request to this post (below) with an email address where I can send the .java source file. (I have no interests in using the address other than to send the code, so know that your address will not be used for any other purpose.)
Wednesday, May 2, 2007
Iterating through a HashMap
Unlike Lists, and ArrayLists should come to mind, Maps don’t have an iterator() method like that provided by the Set or List classes. However, you can iterate through the keys or the key-value elements.
There are a few different types of maps. I’ve used HashMap and TreeMap, but I have yet had the need to use a LinkedHashMap. The ordering of the elements in each differ a bit depending on the type of map.
The method example below will print to the console the key/value pairs contained within an arbitrary map. The method can work with all Maps.
Creating the HashMap and then adding a key/value pair would then look like this:
This would print:
The key value within yerob is NameAddr01
The value of the property within the object is Imgona Choitoya
For completeness, the NameAndAddr class and the TestClass class is shown.
NameAndAddr.java
TestClass.java
There are a few different types of maps. I’ve used HashMap and TreeMap, but I have yet had the need to use a LinkedHashMap. The ordering of the elements in each differ a bit depending on the type of map.
Type Order
---------- ---------------------------------------------------------------
HashMap The location of the entries added to a HashMap is unpredictable
in that it is governed by a hashing algorithm.
TreeMap Order is by key
LinkedHashMap The order of the entries in a LinkedHashMap is governed by the
previously entered element to which it is linked.
The method example below will print to the console the key/value pairs contained within an arbitrary map. The method can work with all Maps.
Object Description
-------------- ---------------------------------------------------
aHashMapOfObject This object is your HashMap containing objects
placed within it by using the HashMap’s put()
method.
pairs Contains the key/value pairs. These values will
mirror the values used on the HashMap’s put()
method.
key The value of the key used for the object placed
into the HashMap.
yerob This is the object initially placed into the
HashMap.
Creating the HashMap and then adding a key/value pair would then look like this:
NameAndAddr nameAddr = new NameAndAddr("Imgona Choitoya");
HashMap aHashMapOfObjects = new HashMap();
aHashMapOfObjects.put("NameAddr01", nameAddr);
Iterator iter = aHashMapOfObjects.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry pairs = (Map.Entry)iter.next();
String key = (String)pairs.getKey();
NameAndAddr yerob = (NameAndAddr)pairs.getValue();
String name = yerob.getName();
System.out.println("The key value within yerob is " + key);
System.out.println("The value of the property within the object is " + name );
}
This would print:
The key value within yerob is NameAddr01
The value of the property within the object is Imgona Choitoya
For completeness, the NameAndAddr class and the TestClass class is shown.
NameAndAddr.java
package testpackage;
public class NameAndAddr {
private String name;
/**
* Constructor
* @param value
*/
public NameAndAddr(String value) {
this.name = value;
}
public String getName() {
return name;
}
public void setName(String value) {
this.name = value;
}
}
TestClass.java
package testpackage;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class TestClass {
/**
* @param args
*/
public static void main(String[] args) {
TestClass test = new TestClass();
test.runIt();
}
/**
*
*
*/
public void runIt() {
NameAndAddr nameAddr = new NameAndAddr("Imgona Choitoya");
HashMap aHashMapOfObjects = new HashMap();
aHashMapOfObjects.put("NameAddr01", nameAddr);
Iterator iter = aHashMapOfObjects.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry pairs = (Map.Entry)iter.next();
String key = (String)pairs.getKey();
NameAndAddr yerob = (NameAndAddr)pairs.getValue();
String name = yerob.getName();
System.out.println("The key value within yerob is " + key);
System.out.println("The value of the property within the object is " + name );
}
}
}
Tuesday, May 1, 2007
Days elapsed since December 31, 1899
There seems to be a common practice to store dates into a database as days since January 1, 1900. The argument advanced is that it's done for performance reasons. The code shown here converts a Java date object to the number of days which have elapsed since that given date.
The following will take a formatted date string and convert it into a date object. It then takes the date object, coverts it into the Julian value measured from December 12, 1899, and converts it back into a date object.
The results of the above test are shown here.
/**
* Accepts a Date object from which the number of days from 1899-12-31 is then
* which select upon table columns containing dates stored with this value.
* The value in columns such as this is actually the number of days from 1899-12-31.
* This method of storing dates can improve database performance, however, during
* research or manual selection of records using a DB tool, the analyst must know
* to use values such as 38700 instead of 2005-12-15 when formatting the SQL
* statement.
*
* @param date
* @return int days
*/
public int getDaysSince1900 (Date date) {
//Tested against the follwing SQL statement:
//select date = DATEDIFF(day, '1899-12-31', '2005-12-15') = 38700
//Today's date at time of test was 2005-12-15 yielding 38700 as diff
Date floorDate = new GregorianCalendar(1899,11,31,00,00).getTime();
long diff = date.getTime() - floorDate.getTime();
long daysSince1900 = diff /(1000*60*60*24);
//Convert to int (Stored in db as int)
String ds = Long.toString(daysSince1900);
int days = Integer.parseInt(ds);
return days;
}
/**
* Accepts an integer value representing the number of days from 1899-12-31 and
* converts it into a Date object.
*
* @param days
* @return dateSince1900
*/
public Date getDateSince1900 (int days) {
Date floorDate = new GregorianCalendar(1899,11,31,00,00).getTime();
long floorAsMilliseconds = floorDate.getTime();
long millisecondsInDay = (1000*60*60*24);
long daysAsMilliseconds = days * millisecondsInDay;
Date dateSince1900 = new Date( floorAsMilliseconds + daysAsMilliseconds );
return dateSince1900;
}
The following will take a formatted date string and convert it into a date object. It then takes the date object, coverts it into the Julian value measured from December 12, 1899, and converts it back into a date object.
Date testDate = null;
int testDays = 0;
try {
String dateAsString = "02/28/2006";
SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy");
testDate = sdf.parse(dateAsString);
//At this point dateObject has no time. Add 12 hours to place it at 12:00:00
testDate.setTime(testDate.getTime() + (12000*60*60));
System.out.println(dateAsString + " -----> " + testDate);
} catch (Exception e) {
}
//Conversion Pass
testDays = test.getDaysSince1900(testDate);
System.out.println(testDate + " -----> " + testDays);
testDate = test.getDateSince1900(testDays);
testDate.setTime(testDate.getTime() + (12000*60*60));
System.out.println(testDays + " -----> " + testDate);
The results of the above test are shown here.
02/28/2006 -----> Tue Feb 28 12:00:00 CST 2006
Tue Feb 28 12:00:00 CST 2006 -----> 38775
38775 -----> Tue Feb 28 12:00:00 CST 2006
Tue Feb 28 12:00:00 CST 2006 -----> 38775
38775 -----> Tue Feb 28 12:00:00 CST 2006
Saturday, April 28, 2007
Comparison using Java's ternary operator
When using Java's ternary operator which allows assignment to a variable based upon one or more Boolean decisions, construct the code in such a way as to mitigate null exception errors.
For example, in the event the field “verified” contains a null value, the following construct results in a null exception error:
This version will return false.
Although acceptable, consider employing a basic if / then construct. The test example below will not fail if verified contains a null and will return false. The construct also allows new logic to be to implemented more efficiently.
For example, in the event the field “verified” contains a null value, the following construct results in a null exception error:
boolean verifiedAsBoolean = "Y".equalsIgnoreCase(verified) ? true : false;
This version will return false.
boolean verifiedAsBoolean = verified.equalsIgnoreCase("Y") ? true : false;
Although acceptable, consider employing a basic if / then construct. The test example below will not fail if verified contains a null and will return false. The construct also allows new logic to be to implemented more efficiently.
If ('Y'.equals(verified)) {
verifiedAsBoolean = true;
} else {
verifiedAsBoolean = false;
}
Wednesday, April 25, 2007
Eclipse tip - Showing more projects in prompt
You can specify that during Eclipse’s startup phase Eclipse should provide a prompt to allow you to select from a list the project you want to work with. By default, this list is limited to showing the last 5 projects you used. Here, I will explain how to specify that the prompt should be shown and how to change the default setting which governs the number of projects in the list.
Showing the prompt.
Once Eclipse is presented, select the following menu path:
Window -> Preferences…
The Preferences Wizard will be presented. From the list provided on the left select:
General, Startup and Shutdown.
Check the option labeled as “Prompt for workspace on startup”. Click the “Apply” then the “Ok” button.
Now, end Eclipse. This is important because if Eclipse is left running you will not be able to edit the file being discussed in the next step.
Changing the default number of projects in the project select list.
Using Windows Explorer, locate the parent directory for your current Eclipse installation, then locate the “configuration” directory and finally the “.settings” directory. For example, my Eclipse instance (Version 3.1.0.) is installed in “Eclipse310”. So the path we want is
“C:\Eclipse310\eclipse\configuration\.settings”.
In the “.settings” directory there is a file named “org.eclipse.ui.ide.prefs”. Open it with your favorite text editor. Here are the contents:
#Fri Mar 31 07:34:04 CST 2006
RECENT_WORKSPACES_PROTOCOL=2
MAX_RECENT_WORKSPACES=10
SHOW_WORKSPACE_SELECTION_DIALOG=true
eclipse.preferences.version=1
RECENT_WORKSPACES=C\:\\Desk\\AppDevJava\\HibernateWorkspace,
C\:\\Desk\\AppDevJava\\DAOGenWorkspace,
C\:\\Desk\\AppDevJava\\TableTestWorkspace,
C\:\\Desk\\AppDevJava\\Development\\TableTests
The line we want to change is the one containing this “MAX_RECENT_WORKSPACES”.
As you can see, it has been changed from the default value of 5 to 10. Change the value for yours to oh, say 15 for example, and save.
Now, the next time you start Eclipse, you will be prompted to select from a list of 15 entries the project you wish to work with.
Monday, April 23, 2007
Reconciling ArrayList contents with the Comparator class
For the current project requiring enhancements to be made to a JSF-based web application, I had to determine what was changed by the user and then write the changes identified into a new table for reporting purposes – an activity journal.
The page had the usual input text boxes and a list-box into which entries as seen within a companion list-box could be selected to add those selections to the second list-box. When the user removed items from the list-box, they were returned to the companion list-box. The problem was to capture those events as “adds’ or “deletes”.
The solution uses two ArrayLists. The first (named “preservedSelectedValues”) holds the list of items in a list-box BEFORE changes. The second (named “selectedValues”), holds the list of items in that list-box after changes. Both ArrayLists hold those entries as SelectItem objects.
Since I need to know what was added to or deleted from the list-box represented by the second ArrayLists it can be treated as a simple reconciliation problem. The brute force code, reeking of COBOL or RPG approaches, is represented here:
The better way is to create a comparator class which has dual responsibilities. The first responsibility is to act as a comparator to assist in sorting the ArrayLists. The second responsibility is to assist in the comparison for differences between the ArrayLists. The comparator class accepts 2 objects and then casts them to SelectItem objects to perform compares against the value properties. The returned value from this exercise is an integer reporting whether it was found to be less than, equal to or greater than. The comparator:
The implementation does the sort followed by the comparison operations. The sprit is the same as the first set of code shown at the top of this post.
There. Much better.
The page had the usual input text boxes and a list-box into which entries as seen within a companion list-box could be selected to add those selections to the second list-box. When the user removed items from the list-box, they were returned to the companion list-box. The problem was to capture those events as “adds’ or “deletes”.
The solution uses two ArrayLists. The first (named “preservedSelectedValues”) holds the list of items in a list-box BEFORE changes. The second (named “selectedValues”), holds the list of items in that list-box after changes. Both ArrayLists hold those entries as SelectItem objects.
Since I need to know what was added to or deleted from the list-box represented by the second ArrayLists it can be treated as a simple reconciliation problem. The brute force code, reeking of COBOL or RPG approaches, is represented here:
//Capture changes.
if (preservedSelectedValues.size() >= selectedValues.size()) {
int foundCount = 0;
for (int i=0; i < preservedSelectedValues.size(); i++) {
boolean found = false;
SelectItem psi = (SelectItem)preservedSelectedValues.get(i);
for(int j=0; j < selectedValues.size(); j++){
SelectItem si = (SelectItem)selectedValues.get(j);
//Found - No change
if(psi.getLabel().equals(si.getLabel()) ) {
found = true;
foundCount++;
break;
}
}
//The item was deleted
if (!found ) {
System.out.println("Deleted : " + psi.getLabel() + " " + psi.getValue());
}
}
if(preservedSelectedValues.size() == foundCount) {
System.out.println("No changes were detected.");
}
if(preservedSelectedValues.size() > foundCount) {
System.out.println("Some items have been deleted.");
}
}
if (selectedValues.size() >= preservedSelectedValues.size()) {
int foundCount = 0;
for (int i=0; i < selectedValues.size(); i++) {
boolean found = false;
SelectItem psi = (SelectItem)selectedValues.get(i);
for(int j=0; j < preservedSelectedValues.size(); j++){
SelectItem si = (SelectItem)preservedSelectedValues.get(j);
//Found - No change
if(psi.getLabel().equals(si.getLabel()) ) {
found = true;
foundCount++;
break;
}
}
//The item was added
if (!found ) {
System.out.println("Added : " + psi.getLabel() + " " + psi.getValue());
}
}
if(selectedValues.size() == foundCount) {
System.out.println("No changes were detected.");
}
if(selectedValues.size() > foundCount) {
System.out.println("Some items have been added.");
}
}
The better way is to create a comparator class which has dual responsibilities. The first responsibility is to act as a comparator to assist in sorting the ArrayLists. The second responsibility is to assist in the comparison for differences between the ArrayLists. The comparator class accepts 2 objects and then casts them to SelectItem objects to perform compares against the value properties. The returned value from this exercise is an integer reporting whether it was found to be less than, equal to or greater than. The comparator:
/**
* Modification History
*
* Date Project Pgmr Description
* -------- --------- ------- -----------------------------------------------
*
*
*/
package com.util;
import java.util.Comparator;
import javax.faces.model.SelectItem;
public class SelectItemComparator implements Comparator {
String name = this.getClass().getName();
/**
* Constructor
*
*/
public SelectItemComparator() {
super();
}
/**
* Compares the suppled SelectItem objects to each other. This method is used
* to resolve sorts and to determine equality.
*
*/
public int compare(Object o1, Object o2) {
SelectItem s1 = (SelectItem)o1;
SelectItem s2 = (SelectItem)o2;
return s1.getValue().toString().compareTo((s2.getValue().toString()));
}
}
The implementation does the sort followed by the comparison operations. The sprit is the same as the first set of code shown at the top of this post.
//Capture changes. First, sort the arraylists containing the SelectItem objects
Collections.sort(preservedSelectedValues, new SelectItemComparator());
Collections.sort(selectedValues, new SelectItemComparator());
//Second, perform the compares
for (int i=0,size=preservedSelectedValues.size(); i < size; i++) {
SelectItem psi = (SelectItem)preservedSelectedValues.get(i);
int pos = Collections.binarySearch(selectedValues, psi, new SelectItemComparator() );
if (pos<0) {
System.out.println("Comparator method. Deleted : " + psi.getLabel() + " " + psi.getValue());
}
}
for (int i=0,size=selectedValues.size(); i < size; i++) {
SelectItem si = (SelectItem)selectedValues.get(i);
int pos = Collections.binarySearch(preservedSelectedValues, si, new SelectItemComparator() );
if (pos<0) {
System.out.println("Comparator method. Added : " + si.getLabel() + " " + si.getValue());
}
}
There. Much better.
Subscribe to:
Posts (Atom)