Thứ Ba, 17 tháng 2, 2009

Day-to-Day Java Development Tools and Techniques

There are some common things that are useful to know in many day-to-day Java development activities. It is especially helpful for someone new to Java development to know where to find this information, but I have found bookmarking these sources to be useful even as I have gained experience developing Java applications. Because one of the primary purposes for me writing a blog is to provide myself with an easy way to find things I am looking for, I am using this blog posting to collect some of these sources of information that I frequently use for Java development in one place. Because these items are related to the development of Java applications rather than to source code, it is not surprising that many of the items are specific to the JVM I am using.


Java Processes: jps

There are many times in Java development when it is important to know which Java processes are running. More specifically, I find myself needing the process ID of Java applications for running other tools against those processes or for terminating processes. There are several Sun JDK development tools, but one of my favorites is the jps tool (JVM Process Status Tool). This "experimental" tool lists the Java processes running on the target HotSpot JVM. As I previously blogged, my favorite use of this tool is jps -lm.


JVM Arguments

Java Application Launcher

The environment a Java application runs in can be significantly tweaked via the use of JVM arguments. In addition, JVM arguments allow for different levels of monitoring and analysis of the executing Java application. The command java -help lists the standard options (standard across different JVM implementations) for the Java application launcher. The command java -X can be used to see the Java application launcher's non-standard (X for extension specific to that JVM) arguments. In the HotSpot JVM, some frequently used extension arguments are -Xms and -Xmx for initial Java heap size and maximum Java heap size respectively.

The -Xbootclasspath option can be used to either prepend (/p) or append (/a) resources to the bootstrap classpath.

If you wish to detect which JVM arguments your currently running Java application is using, you can use the ManagementFactory.getRuntimeMXBean().getInputArguments() call as described in Accessing JVM Arguments from Java. This technique takes advantage of Platform MXBeans available since J2SE 5 (custom MXBeans support was added in Java SE 6).

Two useful sources of information on the JVM parameters available when using Sun's JVM are A Collection of JVM Options and Charles Nutter's Favorite Hotspot JVM Flags. Both of these resources list and describe some/all of the not-recommended-for-the-casual-developer double X arguments (-XX) that are available.

Java Compiler

The javac Java compiler also has command-line arguments that can be of great use. Like the java application launcher, the javac compiler that comes with the Sun Java SDK includes both standard options and non-standard/extension options. These options are displayed in a manner consistent with that used for the application launcher. Non-standard options include bootstrap classpath append and prepend options, setting of endorsed standards path, setting of directory of installed extensions, and enabling and disabling of specific warnings (-Xlint options including -Xlint:path).


Warnings

-Xlint

The warnings reported by the Java compiler can be very useful in identifying things that either may be larger current problems than one realizes or could easily turn into more significant problems. Sun's javac compiler allows one to take relatively granular control of which warnings are enabled and disabled using -Xlint (all warnings are reported when this is used by itself). Specific warnings can be reported by providing the type of warning after the -Xlint: notation. If no reports of warnings are desired (other than those the Java Language Specification requires), the notation -Xlint:none is used.

@SuppressWarnings

One can use the @SuppressWarnings annotation introduced with J2SE 5 to mark in code the warnings that should not be reported when the code is compiled. However, a natural question is which specific warnings can be suppressed in-code using this annotation. It turns out that this is compiler-specific. For Sun's compiler, the available warnings that can be suppressed with @SuppressWarnings are listed here and here.


Java Classpath Issues

Classpath Basics

The Java classpath can be one of the most confusing things for a new Java developer. The classpath can become a complicated mess even for experienced developers when different issues arise due to different classloader behaviors, the presence of slightly different versions of libraries on the classpath, and incorrectly typed classpaths. Fortunately, there are several tools to help understand and better work with the classpath.

For those just learning Java and Java classpaths, a good starting point is to read Elliotte Rusty Harold's articles Managing the Java Classpath (Windows) and Managing the Java Classpath (Unix). These articles provide a good overview of the options one has for setting a classpath, why dynamically setting the classpath is preferred over using an environment variable, and provides the basics of how classpaths work with Java packages.

Because the Sun Java compiler will ignore an entry on the classpath that it cannot resolve, it is easy to think that one has correctly typed in a classpath entry even if it is incorrectly typed. The -Xlint:path extension option instructs the Java compiler to report any classpath entries that cannot be resolved.

The JARs, .class files, and other resources specified on a command-line classpath using -classpath or -cp are typically not the only resources on an application's classpath. Other sources of classpath information that must be considered include the standard Java classpath (for standard Java classes), jre/lib/ext, and jre/lib/endorsed (uses bootstrap classpath). In addition classpaths may be different when using an IDE and are different when using web servers and application servers (classpath is typically determined by the contents of the WAR file and EAR file in those cases).

ClassNotFoundException Versus NoClassDefFoundError

One thing that can be a little tricky when first learning Java is distinguishing between the ClassNotFoundException and the NoClassDefFoundError. The Javadoc API documentation for each of these explains their use and why they might occur. In most cases, the easy way to differentiate between the two is that the ClassNotFoundException indicates that a class needed for compilation cannot be found on the classpath and a NoClassDefFoundError indicates that the matching class that was found at compile time cannot be found at runtime. This differentiation between compile time and run time can be useful in figuring out when the classpath entry is missing.

It is also interesting to note that while ClassNotFoundException is a checked exception extending Exception directly, NoClassDefFoundError is actually an Error rather than an Exception. Note that ClassNotFoundException can actually be encountered during runtime in a variety of situations such as the runtime on-the-fly compilation of JavaServer Pages, reflection, and, as the API states, "when an application tries to load in a class through its string name." Generally speaking, the causes of NoClassDefFoundError are more diverse and difficult to resolve than those of ClassNotFoundException.

Some interesting and in-depth articles on class loading are available in the Demystifying Class Loading Problems series. Other references regarding the difference between these two exceptions are available at Difference Between ClassNotFoundException and NoClassDefFoundError, What is the Difference Between ClassNotFoundException and NoClassDefFoundError?, Java Fanatics: NoClassDefFoundError versus ClassNotFoundException, and ClassNotFoundException and NoClassDefFoundError.

UPDATE (2 March 2009): Identifying an Instance's Original Classpath Definition

UPDATE (2 March 2009): This subheading and entire paragraph have been added since the original post. Some of the NoClassDefFoundErrors one might run into can be traced to multiple definitions of the same class on the classpath with slight differences. One way to see which class definition a loaded class is using is described in another blog post of mine.



System Properties

Java provides some standard properties that can be used for several different benefits. An often used property from this set include line.separator for a platform-independent reference to a new line. It is not easy to remember all of the available system properties, especially those that are not used often. Fortunately, several resources either list these or provide code examples of how to see a list of them. For example, Java Standard System Properties lists them, How to Print All the Java System Properties demonstrates how to programmatically list them, and Java: System Properties both demonstrates how to get the properties via code and lists them. The Javadoc-based API documentation for System.getProperties() lists the system properties that are always available. I've even been known to blog on properties before.

The Platform MXBean RuntimeMXBean provides a RuntimeMXBean.getSystemProperties() method that can be used to see the system properties as well.


View Swing GUI's Hierarchy with CTRL-SHIFT-F1

A nifty trick with Swing-based applications is the ability to use CTRL-SHIFT-F1 to see the Swing GUI's hierarchy. This is further demonstrated and explained in the Tech Tip Ctrl-Shift-F1 in Swing Applications.


serialver

The command-line serialver tool is useful for generating servialVersionUID attributes on classes that implement the Serializable interface. I have blogged previously on the serialver tool.


jar Tool and JAR Files

jar Tool

With the prevalence of IDEs and Ant and Maven for building Java code, it is easy to forget about the jar command. However, knowledge of the jar command can be useful for writing and running simple tests and examples. One of jar's characteristics that makes it most useful is the fact that it uses the ZIP compression format and uses similar syntax to the tar command. The versatile jar command can even be used to display the contents of an Adobe AIR .air file.

Executable JAR Files

JAR files can be made executable so that they can be run with a command line java -jar someJar.jar without the need to specify classpath information or the main executable class in the JAR. The classpath and the main class to execute do not need to be explicitly stated because they are embedded in the executable JAR's manifest file with the Class-Path and Main-Class name/value pairs respectively. There are a few details to be aware of when using executable JARs (such as specifying the class path entries via directories relative to the location of the executable JAR file) and these details are covered in the blog posting Executable Jar File in Java.

JAR Manifest File Information

You can place essentially any name/value pair in a manifest file on its own line. While virtually any names and values can be used, there are some standard and accepted manifest name/value pairs one might wish to use. These include Class-Path, Main-Class, package version information (Specification-Title, Specification-Version, Specification-Vendor, and implementation equivalents). The Manifest file is also used to seal packages in JAR files. See also the Wikipedia entry on Manifest File for more details.


JVM JMX Instrumentation

The JVM itself has been instrumented with Java Management Extensions (JMX) support since J2SE 5. This provides valuable information regarding the JVM and applications running in the JVM. Sun provides JConsole (since J2SE 5) and VisualVM (since Java SE 6 Update 7) with their SDK for easy developer monitoring of the JVM and its deployed applications.

Platform MXBeans have been provided with the Sun JVM since J2SE 5 and provide a wide variety of details regarding the JVM. Platform MXBeans provide information on JVM topics such as thread contention, operating system details, memory analysis, and logging management. As described earlier, the RuntimeMXBean provides interesting information such as the classpath in effect, the boot classpath, system properties, and JVM vendor and specification details. The available Platform MXBeans are described in greater detail in Using the Platform MBean Server and Platform MXBeans.

Java EE application servers also provide information via JMX.


UPDATE (18 February 2009) Generating a Stack Trace

UPDATE (18 February 2009) Eyal Lupu reminded me (see feedback) of a very handy tool during debugging (especially when processes are hanging). Forced generation of a stack trace with a SIGQUIT, CTRL+\, or CTRL-BREAK (Windows) is described in greater detail in An Introduction to Java Stack Traces.


Documentation

Some of the Java documentation resources that I use most often include the overall JDK 6 Documentation (includes links to many of the other documents referenced in this blog posting), the Java SE 6 API documentation, the Java EE 5 API documentation, and specialized documentation such as the Spring Reference Manual and the Oracle Technology Network JPA Annotations Reference. Perhaps my favorite tool for finding useful documentation is the Google search engine.

Thứ Bảy, 14 tháng 2, 2009

Classic Movie Quotes Applied to Software Development



UPDATE (9 March 2009): This posting has been referenced and quoted in the CNN Entertainment article You Talkin' to Me? Film Quotes Stir Passion.




In this blog posting, I look at some classic movie quotes that can apply to software development. In several cases, I have actually heard them used in software development situations. In general, these movie quotes are from movies I like (or at least have seen) and can be applied to software development. It is not all that surprising that the movie quotes that we all seem to be able to relate to also relate specifically to software development.


1. "What we've got here is failure to communicate."

This quote from 1967's Cool Hand Luke (#11 in AFI's Top 100 Quotes) summarizes one of the most common problems facing software developers. Problems with communication can plague all stages of software development from learning customer requirements to development to testing to product delivery. In fact, a large percentage of the most significant problems I have seen in large software development projects can be traced back in some degree to a break-down in communications. These include things like redundant work being done, interfaces and contracts not being met, misunderstandings about requirements and expected functionality, etc.


2. "You're gonna need a bigger boat."

This quote (#35 in the AFI Top 100 Quotes) from the 1975 movie Jaws was the perfect quote for representing this movie. Jaws is a fantastic suspense movie with an understated, Hitchcock-like building of suspense throughout the movie mingled with briefly intense scenes and highlighted with John Williams' famous and famous two-note music. This quote seems understated at first glance, but really does sum up the plight of the main characters and their dangerous situation with this greatest of Great Whites. It also serves as a great transition from the extraordinary fear built up through imagination of what the shark must look like to new scenes where the shark is visible.

We often run into our own situations like this in software development. We often find ourselves needing more memory, more disk space, more bandwidth, or more of other types of resources to do our jobs and to maintain customers' experiences. There are numerous well-known and real-life examples of this including the inability to purchase Colorado Rockies World Series tickets and the initial server overload for JavaFX 1.0 downloads.


3. "Show me the money!"

This quote from the 1996 movie Jerry Maguire (#25 in AFI's Top 100 Quotes) is one most of us can easily relate to. With current economic times like they are, this may mean more than ever to us. The fact that money is at the root of so much that is motivating is also a reason that it is likely that the "free speech" part of open source will remain as important or more important than the "free beer" part of open source in the future. While there are many great open source products that are used without charge, many significant open source projects are run by organizations that (not undeservedly) expect to earn some revenues from their association with these products and related services. Examples include SpringSource, Sun, and many others.

This quote almost makes up for the other well-known quote in that movie: "You had me at 'hello'" (#52 in AFI Top 100 Quotes).


4. "A person is smart. People are dumb, panicky dangerous animals and you know it."

The 1997 movie Men in Black is full of great quotes including this one. Edwards (soon to be Jay) is asking Kay why the secret Men in Black don't tell the ordinary people of Earth about the aliens living among them. This quote is Kay's explanation and it often applies in software development as well.

It is easy in our rush to stay current and relevant to make poor choices that we would not do individually when given the opportunity to think rationally about the situation. The rush to apply EJB 1.x/2.x where it wasn't needed is one example of this.

Terms such as mob mentality, herd behavior, and peer pressure largely have negative connotations because they often imply allowing poor judgment based on group thinking to override potentially clearer and superior individual judgment. I have written in a previous blog posting about The Lemming Effect and The Emperor's New Clothes Effect in software development. Both of these dysfunctional software development motivations are related to negative group think.


5. "Houston, we have a problem."

This is a momentous quote (#50 in AFI's Top 100 Quotes) in a terrific movie (1995's Apollo 13) based on real events (the actual Jim Lovell quote was "Houston, We've had a problem."). It is now used all the time to indicate a (usually) less serious problem than when it was originally used. For example, see this completely unrelated example. Because software and hardware surprises us all the time and then we throw our own human-caused errors into the mix, we seem to get many opportunities to apply this quote. The calm coolness portrayed by Tom Hanks in the movie and the power of many dedicated engineers in Houston working on the problem are a reminder of how best to solve problems and are an inspiration. Lovell has called that Apollo 13 mission "a successful failure."


6. "I had to keep digging ... without a shovel."

This quote comes from the 1985 movie Fletch. It sometimes feels like we in the software development community find ourselves in situations in which we are tying to identify and resolve a nasty bug without the appropriate tools. This can be especially likely in cases such as runtime logic errors, in multi-threaded environments, and when using languages that are too new (or too old) to have good debug tool support.


7. "My Precious"

With the exception of a few things that really bothered me (most notably the mistreatment of Faramir's character as having seemingly less integrity than in the books), I generally enjoyed the 2001-2003 adaptation of J.R.R. Tolkein's The Lord of the Rings trilogy. I thought that the character Gollum was particularly well done. The movie version of Gollum wanted The One Ring as badly as the book version did in my imagination.

In software development, it is good to take ownership of things, have pride in what we do, and instill craftsmanship into our work. However, we can take it too far to where we refuse to admit anything is wrong with what we've done or insist ours is the only way to go. If I work long and hard on piece of code and later find out that it is either being replaced by an alternative or not being used altogether, it is easy to start acting greedily and with distrust like Gollum. It is not easy being told your brainchild is ugly, but sometimes it just might be. This quote earned #85 in AFI's Top 100 Quotes.


8. "You fell victim to one of the classic blunders."

The complete quote (not listed in the header due to its length) from the 1987 movie The Princess Bride is: "You fell victim to one of the classic blunders. The most famous is: 'Never get involved in a land war in Asia.' But, only slightly less well known is this: 'Never go in against a Sicilian, when death is on the line!'"

It is difficult to think of a movie with more quote-worthy lines than this one. I chose this particular quote from this movie because we are all aware of some of the classic software development blunders such as copying and pasting identical code in multiple places, hard-coding values, swallowing exceptions rather than handling them, etc., but there continues to be enough people committing these blunders to lead to numerous blog postings, articles, and discussions on the blunders. Steve McConnell has collected three dozen higher-level classic blunders in his Classic Mistakes Enumerated.


9. "I think all you need is a small taste of success, and you will find it suits you."

I have blogged before about the importance of confidence in software development. In this quote from the 1985 movie Better Off Dead, Monique is explaining to the rather pathetic Lane the importance of having a little success to gain confidence and a real desire to work to achieve more of the same success. This seems like good advice for all of us, but I think it is especially useful for new software developers. It can be overwhelming to start working with much more experienced software developers, but a small taste of success early on can be very helpful and lead to a long career full of successes. This is also a reminder to more experienced and senior developers that we can help those who are new to development to get a taste of success.

This is another quote-filled movie with "I want my two dollars" not the least among them.


10. "Frankly, my dear, I don't give a damn."

This quote, which earned the top spot in AFI's Top 100 Quotes occurs at a point in the 1939 epic Gone with the Wind when it is very easy to understand this sentiment coming from the Rhett Butler character. It is equally easy to sometimes find ourselves feeling the same way. In fact, it is sometimes good to give up worrying about the littlest of things and let some things go. In particular, I have seen situations in which someone with no formal power but high degrees of expert power continually fight against those with formal power (such as managers and clients). These are almost always losing causes.

While valid discussions and even technical disagreements are useful in coming to the best solutions, there are times when the fight is not worth the cost. This is especially true in situations where multiple recommendations are sufficient and it is only a matter of which one is slightly preferable to the others. I have witnessed numerous situations in which the debate and delay in beginning implementation of a solution has taken ten times longer than the difference of development time between two competing options.

I have observed that effective software developers do care deeply about their work and the product they create. That being said, the most effective of these effective software developers also know when to pick their battles and when to just let it go because it's not worth it.

Another Gone with the Wind quote that fits well is Scarlett's words, "After all, tomorrow is another day!" (#31 on AFI Top 100 Quotes). This is good advice because bad things often aren't as bad the next day as they are the day they happen.


Conclusion

In this blog posting, I have looked at the quotes from ten different movies that apply to software development even though none of the movies listed were specifically focused on computers or software development. In fact, while I enjoy many of the computer-oriented movies such as War Games (1983), it is also true that many of these are completely unrealistic and the computer is often the bad guy. If nothing else, I hope to use this blog posting to prove to others that watching movies is not a waste of time. I'm going with the story that watching movies actually makes me a better software developer. It's time now to go watch Star Wars. May the force be with me (#8).

Thứ Năm, 12 tháng 2, 2009

Thankful for User Groups

As the Rocky Mountain Oracle Users Group (RMOUG) Training Days 2009 was wrapping up, I couldn’t help but think about this event that the RMOUG volunteers pull off each year. That thought naturally led to thoughts of other users groups that I have benefited from and to a renewed appreciation for the volunteers that make these user groups successful. As I blogged about previously, I believe that Java-related users groups are one of the Java resources that were missed in this article on resources for newer Java developers. These user groups can provide significant benefit to more experienced developers as well.

RMOUG’s annual Training Days conference provides an example of what many user groups could become, but only at the cost of countless volunteer hours. This year’s edition of Training Days was the 18th version (my tenth time attending and eighth time presenting). While all the editions that I have attended have been full of insightful speakers and informed colleagues and fellow attendees, there has been a definite increase in recent years in the number of “big names” presenting at RMOUG Training Days. Dan Norris, a well-known Oracle DBA himself, commented on this in his blog.

When I look at RMOUG’s success in turning this local/metro conference into a regional success and then into a national success and now into an international success, I believe there are some lessons learned regarding what it would take other user groups to get to this point. RMOUG Training Days attracts some of the biggest names in the World of Oracle.


Consistency over Time

One explanation for RMOUG’s success in attracting the heavy hitters of the Oracle world is the long tradition of its successful conferences. The consistency of the conference to provide quality training related to Oracle at a very low cost has led to a well-deserved reputation that seems to grow each year.


Hours and Hours of Volunteer Activity

Another reason that RMOUG has been so successful is the dedicated volunteers that make the organization run. I see this too in the people who run other local users groups, but RMOUG seems to enjoy the benefits of more of these people being actively involved. I put much effort and significant hours into preparing for my RMOUG Training Days presentations. However, I realize that even the volunteers who do not present may spend that many hours on the organization. Furthermore, many of the board members and other most active volunteers also present.

Speakers who are consultants and/or authors do often have at least an outside chance of some monetary gain from speaking; they may be able to indirectly advertise their consulting services or books to the audience that is interested in that topic. However, many volunteers have no expectations of this type of compensation either because they don’t provide consulting services or because they don't have a book or because they are not in a position to advertise that if they did. That being said, it is also worth noting that most consultants and authors would likely not present if the sole purpose of presenting was financial gain. There are usually easier ways to achieve that. Typically, they also want to present to share their knowledge.


Don’t Forget the Locals

While it is a huge benefit to have the prominent Oracle experts present and share ideas, I believe that the strongest user groups rely heavily on the local practitioners as well. For one thing, it is often the local practitioners who provide consistent quality over time to get the attention of non-local experts. It is also the local practitioners who can attend regular meetings that are not as large or well-attended as Training Days. Of course, no user group can succeed without the countless hours provided by the local volunteers. Finally, there is a really nice personal touch to knowing people in the field who live and work in the same general area. It is also worth noting that user groups in metropolitan areas with strong technology sectors enjoy locals who are well-known experts.


Don’t Forget the Little People

As mentioned before, it is a particular benefit of Training Days to meet and hear presentations from some of the most well-known people in the Oracle community. However, I have heard some remarkably thought-provoking presentations from people who are not well-known. In fact, even with a large number of well-known experts attending, a conference as large as RMOUG Training Days requires interesting local practitioners to provide the wide breadth and depth of topics.


The Significance of Sponsors

Sponsors can also make a user group meeting or conference more successful. Sponsors typically pay for the right to advertise and that offsets some of the cost to the participants. The usual trade-off model applies here: more advertising means lower costs to the end user.


Nothing Is Free

I have attended many great presentations in conjunction with several local users groups, but RMOUG definitely seems to be the most successful in terms of participants and clout. I believe that RMOUG’s success has not come easily or without significant sacrifice. The sacrifice of time, resources, and sharing of talents and knowledge by many dedicated individuals has helped RMOUG and its annual training extravaganza to become what it is today.

Thứ Hai, 9 tháng 2, 2009

Flex: Proxied HTTPService and REST

My article Java EE and Flex: A compelling combination, Part 2 was published on JavaWorld last week. In that article, I briefly discussed some of the extra features that BlazeDS adds to HTTPService beyond what is support with Flex's HTTPService out of the box without BlazeDS.

I have previously blogged on some of these advantages of HTTPService with BlazeDS. In this blog posting, I plan to focus on something outside of the scope of both the article and previously blog posting: how BlazeDS allows the Flex HTTPService to be used with HTTP methods other than GET and POST.

In the JavaWorld article, I wrote the following: "Another advantage of using HTTPService in conjunction with BlazeDS is that you can use HTTP methods beyond GET and POST (the only two supported in HTTPService without the proxy service)." I will use the remainder of this blog posting to expand on this and illustrate this with examples.

To illustrate the differences between HTTP methods supported by non-proxied HTTPService (out-of-the-box without BlazeDS) and proxied HTTPService (with BlazeDS), I have put together a very simple Flex application that connects to a back-end HTTP-exposed server using both non-proxied and proxied HTTPService clients. The main MXML source code for this simple illustrative example is shown next.

FlexHttpClient.mxml


<?xml version="1.0" encoding="UTF-8" ?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:dustin="components.*"
width="800" height="500"
applicationComplete="invokeServer('GET');">

<mx:Script>
import mx.controls.Alert;
import mx.events.ItemClickEvent;
import flash.events.MouseEvent;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.utils.ObjectUtil;

include "includes/HttpMethodHandling.as";
include "includes/ResponseCodeHandling.as";
</mx:Script>

<mx:HTTPService id="httpServiceProxied"
useProxy="true"
resultFormat="object"
destination="DefaultHTTP"
fault="faultHandler(event);"
result="proxiedResultHandler(event);">
<mx:request>
<id>5</id>
</mx:request>
</mx:HTTPService>

<mx:HTTPService id="httpService"
useProxy="false"
resultFormat="object"
url="http://localhost:8080/RESTfulFlex/httpServer"
fault="faultHandler(event);"
result="nonProxiedResultHandler(event);">
<mx:request>
<id>5</id>
</mx:request>
</mx:HTTPService>

<mx:HTTPService id="httpServiceResponseCodes"
useProxy="false"
resultFormat="object"
method="GET"
url="http://localhost:8080/RESTfulFlex/responseCodes"
fault="responseCodesFaultHandler(event);"
result="resultCodeHandler(event);">
<mx:request>
<requestedCode>{desiredHttpCode.text}</requestedCode>
</mx:request>
</mx:HTTPService>

<mx:HTTPService id="httpServiceResponseCodesProxied"
useProxy="true"
resultFormat="object"
method="GET"
destination="DefaultHttpResponseCode"
fault="responseCodesFaultHandler(event);"
result="proxiedResultCodeHandler(event);">
<mx:request>
<requestedCode>{desiredHttpCode.text}</requestedCode>
</mx:request>
</mx:HTTPService>

<mx:TabNavigator id="tabs" width="750" height="450">
<mx:Panel id="mainPanel"
title="Flex HTTPService Demonstrated"
label="HTTPService Demonstrated">
<mx:Form>
<dustin:ProxySelectorFormItem label="To Proxy or Not To Proxy?"
selectorHandler="setProxyStatus" />
<mx:FormItem label="Select HTTP Method">
<mx:RadioButtonGroup id="method" itemClick="invokeHttpMethod(event);"/>
<mx:RadioButton groupName="method" id="get" label="GET"/>
<mx:RadioButton groupName="method" id="post" label="POST"/>
<mx:RadioButton groupName="method" id="put" label="PUT"/>
<mx:RadioButton groupName="method" id="remove" label="DELETE"/>
<mx:RadioButton groupName="method" id="options" label="OPTIONS"/>
<mx:RadioButton groupName="method" id="trace" label="TRACE"/>
</mx:FormItem>
<mx:FormItem label="HTTP Method Invoked">
<mx:Label id="serviceResults" />
</mx:FormItem>
</mx:Form>
</mx:Panel>
<mx:Panel id="responseCodePanel"
title="HTTP Response Codes"
label="HTTP Response Codes">
<mx:Form>
<dustin:ProxySelectorFormItem
label="Proxied?"
selectorHandler="setResponseCodesProxyStatus" />
<mx:FormItem label="Desired Return Code">
<mx:TextArea id="desiredHttpCode" />
</mx:FormItem>
<mx:FormItem>
<mx:Button label="Submit Desired Response Code"
click="invokeHttpResponseCodes(event);"/>
</mx:FormItem>
<mx:FormItem label="Returned Text">
<mx:Text id="returnedHttpResponse" />
</mx:FormItem>
</mx:Form>
</mx:Panel>
</mx:TabNavigator>

</mx:Application>


The above MXML file contains most of the layout for the simple application and includes the configuration of the proxied and non-proxied instances of HTTPService. You may have noticed that there are actually four instances of HTTPService. Two instances are the proxied and non-proxied instances for testing the different HTTP methods while the other two instances are the proxied and non-proxied instances used to test handling of HTTP response codes. I will not cover the latter two in this post, but plan to cover response code handling in a future post.

I intentionally placed the static layout MXML code in the file above. I factored the event handling code, written in ActionScript, into two separate files that are included by the MXML code above. These two ActionScript files are shown next.

HttpMethodHandling.as

/** true if proxied service is to be used; false otherwise. */
[Bindable] private var proxiedService:Boolean = false;

/**
* Invoke HTTPservice with HTTP method specified by parameter.
*
* @param httpMethod HTTP method to be used.
*/
private function invokeServer(httpMethod:String):void
{
if (proxiedService)
{
httpServiceProxied.method = httpMethod;
httpServiceProxied.send();
}
else
{
httpService.method = httpMethod;
if (httpMethod == "POST")
{
const dummyObject:Object = { "key" : "value" };
httpService.send(dummyObject);
}
else
{
httpService.send();
}
}
}

/**
* Handler for HTTP method selected from radio button group
*
* @param event Event associated with clicking of radio button to select HTTP
* method.
*/
private function invokeHttpMethod(event:ItemClickEvent):void
{
serviceResults.text = "Loading ...";
const selectedHttpMethod:String = event.currentTarget.selectedValue;
invokeServer(selectedHttpMethod);
}

/**
* Handler for radio button group used to determine if proxied or non-proxied
* HTTPService is to be used.
*/
private function setProxyStatus(event:ItemClickEvent):void
{
const proxyStatus:String = event.currentTarget.selectedValue;
proxiedService = (proxyStatus == "BlazeDS Proxied");
}

/**
* Fault handler for faults encountered during a service call.
*
* @param event Fault Event needing to be handled.
*/
private function faultHandler(event:FaultEvent):void
{
serviceResults.text = "Failure trying to access service.\n"
+ event.fault.faultString + "\n" + event.fault.faultDetail;
}

/**
* Results handler for result of proxied HTTPService invocation.
*
* @param event Result Handler for proxied HTTPService call.
*/
private function proxiedResultHandler(event:ResultEvent):void
{
serviceResults.text = ObjectUtil.toString(httpServiceProxied.lastResult);
}

/**
* Results handler for result of non-proxied HTTPService invocation.
*
* @param event Result Handler for non-proxied HTTPService call.
*/
private function nonProxiedResultHandler(event:ResultEvent):void
{
Alert.show("Non-Proxied Result Handler Accessed!");
serviceResults.text = ObjectUtil.toString(httpService.lastResult);
}



ResponseCodeHandling.as


/** true if proxied service is to be used; false otherwise. */
[Bindable] private var responseCodesProxiedService:Boolean = false;

/**
* Handler for invoking of response codes service.
*
* @param event Event associated with clicking of radio button to submit
* response code.
*/
private function invokeHttpResponseCodes(event:MouseEvent):void
{
returnedHttpResponse.text = "Loading ...";
if (responseCodesProxiedService)
{
httpServiceResponseCodesProxied.send();
}
else
{
httpServiceResponseCodes.send();
}
}

/**
* Handler for radio button group used to determine if proxied or non-proxied
* HTTPService is to be used for response code service.
*/
private function setResponseCodesProxyStatus(event:ItemClickEvent):void
{
const proxyStatus:String = event.currentTarget.selectedValue;
responseCodesProxiedService = (proxyStatus == "BlazeDS Proxied");
}

/**
* Results handler for result of service call for HTTP response code.
*
* @param event Result Handler for non-proxied response code reply.
*/
private function resultCodeHandler(event:ResultEvent):void
{
returnedHttpResponse.text =
"SUCCESS: " + ObjectUtil.toString(httpServiceResponseCodes.lastResult);
}

/**
* Results handler for result of service call for HTTP response code that is
* based on a proxied call.
*
* @param event Result Handler for proxied response code reply.
*/
private function proxiedResultCodeHandler(event:ResultEvent):void
{
returnedHttpResponse.text =
"SUCCESS: " + ObjectUtil.toString(httpServiceResponseCodesProxied.lastResult);
}

/**
* Fault handler for faults encountered during a service call on the
* response codes service.
*
* @param event Fault Event that needs to be handled.
*/
private function responseCodesFaultHandler(event:FaultEvent):void
{
returnedHttpResponse.text = "ERROR: Failure trying to access service.\n"
+ event.fault.faultString + "\n" + event.fault.faultDetail;
}



The MXML file and two ActionScript files shown above constitute the client side of this example. Now, a HTTP-exposed server side is required to complete the example. This is implemented using a Java servlet. The servlet, which passes back a simple String indicating that a particular HTTP method has been called, is shown next.

SimpleHttpServer.java


package dustin.flex.rest;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.logging.Logger;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


/**
* Simplistic Java EE server-side application intended for demonstration of
* Flex HTTPService capabilities with and without BlazeDS.
*
* @author Dustin
*/
public class SimpleHttpServer extends HttpServlet
{
/** Set up java.util.Logger. */
private static Logger LOGGER =
Logger.getLogger("dustin.flex.rest.SimpleHttpServer");

/**
* Servlet method responding to HTTP GET methods calls.
*
* @param request HTTP request.
* @param response HTTP response.
*/
@Override
public void doGet( HttpServletRequest request,
HttpServletResponse response ) throws IOException
{
LOGGER.warning("[DUSTIN] doGet() Accessed!");
final PrintWriter out = response.getWriter();
final String requestId = request.getParameter("id");
out.write("GET method (retrieving data) was invoked with ID " + requestId + "!");
}

/**
* Servlet method responding to HTTP POST methods calls.
*
* @param request HTTP request.
* @param response HTTP response.
*/
@Override
public void doPost( HttpServletRequest request,
HttpServletResponse response ) throws IOException
{
LOGGER.warning("[DUSTIN] doPost() Accessed!");
final PrintWriter out = response.getWriter();
out.write("POST method (changing data) was invoked!");
}

/**
* Servlet method responding to HTTP PUT methods calls.
*
* @param request HTTP request.
* @param response HTTP response.
*/
@Override
public void doPut( HttpServletRequest request,
HttpServletResponse response ) throws IOException
{
LOGGER.warning("[DUSTIN] doPut() Accessed!");
final PrintWriter out = response.getWriter();
final String requestId = request.getParameter("id");
out.write("PUT method (inserting data) was invoked with ID " + requestId + "!");
response.setStatus(HttpServletResponse.SC_CREATED);
}

/**
* Servlet method responding to HTTP DELETE methods calls.
*
* @param request HTTP request.
* @param response HTTP response.
*/
@Override
public void doDelete( HttpServletRequest request,
HttpServletResponse response ) throws IOException
{
LOGGER.warning("[DUSTIN] doDelete() Accessed!");
response.setStatus(HttpServletResponse.SC_OK);
final PrintWriter out = response.getWriter();
final String requestId = request.getParameter("id");
out.write("DELETE method (removing data) was invoked with ID " + requestId + "!");
}

@Override
public String getServletInfo()
{
return "Provide examples of HTTP methods invoked by clients.";
}
}



With this servlet in place, we can now look at the differences between HTTP method handling for HTTPService with and without BlazeDS. I will be using GlassFish to host all server-side code shown in this blog posting.

Without BlazeDS (or similar proxy server support), the Flex HTTPService only supports the GET and POST methods. I previously blogged about even the POST in a non-proxied HTTPService being treated like a GET in some cases. The example code above does use a body in the POST request to ensure that it is treated like a POST. As the following screen snapshots indicate, all of the other major HTTP methods (PUT, DELETE, OPTIONS, TRACE) are treated as GET methods. POST is treated like a POST.

Non-Proxied HTTPService GET



Non-Proxied HTTPService POST



Non-Proxied HTTPService PUT



Non-Proxied HTTPService DELETE



Non-Proxied HTTPService OPTIONS



Non-Proxied HTTPService TRACE





In the non-proxied HTTPService examples illustrated above, all of the HTTP methods were treated as GET except for the POST method. As stated earlier, even that can be treated as a GET if no body is included in the request.

The next series of screen snapshots show the same HTTPService calls as before, but with a BlazeDS proxy used now.

Proxied HTTPService GET



Proxied HTTPService POST



Proxied HTTPService PUT



Proxied HTTPService DELETE



Proxied HTTPService OPTIONS



Proxied HTTPService TRACE




The results shown in the sample Flex client for BlazeDS-proxied HTTPService instances are more interesting than the non-proxied counterparts. The GET and POSt work the same for both proxied and non-proxied, but the other HTTP methods have different results in the proxied versions than in the non-proxied versions. In the screen snapshots of the results of the TRACE and OPTIONS calls, I took advantage of the Flex Label feature that displays an automatic truncation tip when the contents of the Label are larger than the label and no tooltip is provided for the Label. We can see that when BlazeDS is used, HTTPService can "see" the options returned by the server and can see the traced route.

The results for PUT and DELETE are not quite as encouraging. While we see "null" in the text field, all is not lost. The result handler is called when the HTTP PUT or DELETE invocation returns, but the text does not get populated. Also, we can see that the PUT and DELETE are being called when we look at the GlassFish log as shown in the next screen snapshot.



In the GlassFish log record shown immediately above, log record number 300 represents the GET initially performed when the Flex application's applicationComplete event is triggered. Log record 301 is the explicit non-proxied GET and log record 302 is the explicit non-proxied POST. Log records 303 through 306 are the PUT, DELETE, OPTIONS, and TRACE non-proxied calls treated like GETs. Log record 307 is the BlazeDS-proxied GET and log record 308 is the BlazeDS-proxied POST. Log record 309 and log record 310 are clearly the proxied PUT and DELETE calls as evidenced by the WARNING text. This also proves that the server-side doPut() and doDelete() methods were in fact invoked.

It is nice to be able to use Flex's HTTPService with BlazeDS to use a more complete range of the standard HTTP methods. I would like to find out if there is a way to return a non-null body from PUT and DELETE calls so that the REST-inspired concept of Hypermedia as the Engine of Application State (HATEOAS) might be more fully realized for those two HTTP methods.

As mentioned earlier, BlazeDS also makes HTTPService more useful by improving its handling of HTTP response codes similarly to how it improves HTTPService's handling of HTTP method differentiation. I hope to cover this BlazeDS-improved handling of HTTP response codes in a future blog post.


Addition (March 2010)

Christian Junk pointed out that I had not included the piece of MXML code defining ProxySelectorFormItem. That code is shown next and this sample comes from a file called ProxySelectorFormItem.mxml.


<?xml version="1.0" encoding="utf-8"?>
<mx:FormItem xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:dustin="components.*">
<mx:Script>
<![CDATA[
[Bindable] public var selectorHandler:Function;
[Bindable] public var proxiedLabel:String = "BlazeDS Proxied";
[Bindable] public var nonProxiedLabel:String = "Not Proxied";
]]>
</mx:Script>

<mx:RadioButtonGroup id="proxyStatus" itemClick="{selectorHandler(event)}"/>
<mx:RadioButton groupName="proxyStatus" id="proxy" label="{proxiedLabel}" />
<mx:RadioButton groupName="proxyStatus" id="nonproxy" label="{nonProxiedLabel}" />

</mx:FormItem>



Conclusion

BlazeDS allows Flex developers to use a wider range of HTTP methods. This is one of the many advantages that BlazeDS offers for proxied HTTPService when compared to non-proxied HTTPService.


Additional Resources

There are several good blog postings on using Flex with HTTP and REST. A particularly insightful resource is the StackOverflow thread Is It Feasible to Create a REST Client with Flex?.

Thứ Bảy, 7 tháng 2, 2009

A Refreshingly Honest Blog Post

One of the advantages of writing a blog post or an article is that the author has an opportunity to think about what he or she writes before publishing it. Presenting at a conference is always more risky because one might have a momentary memory lapse or gap in judgment and say something he or she will later regret. While this can happen in writing, there are usually a few more guards (such as a in a blog posting) to many guards (such as in peer-reviewed writing). One common thread we often see in both writing on technical subjects and presentations on technical subjects is an implied expertise in the subject material and often an associated implication that the author can do no wrong.

It is understandable that an author or presenter would not want to admit to his or her shortcomings or lack of knowledge. Even when a person really is an expert in an area, any sign of weakness can be and unfortunately often is interpreted as a significant hole in that person's expertise. It is natural for authors to only want to present their good side and to only write about or talk about things they understand.

The reason that I include the word "speculations" in my blog title is because I often do mix experiences, observations, thoughts, and cogitations with some speculation. This is also why I include a disclaimer on my blog that essentially states that while I try to be accurate in what I write, there is no way I can guarantee that everything I write will always be correct. I do not knowingly write something that is false or incorrect, but that doesn't mean that I don't make mistakes.

I think much can be learned from mistakes. We can certainly learn from our own mistakes, but it is even better if we can learn from someone else's mistakes without paying the same price that someone else had to pay. The unfortunate side effect of our seeming human nature to emphasize our strengths and hide our weaknesses means that we can lose some benefits of lessons learned from poor choices.

With all of this in mind, I found Ioannis Cherouvim's recent blog posting The * Stupidest Things I've Done in My Programming Job to be especially refreshing. It is not easy to post mistakes one has made publicly and open oneself to potential ridicule. There are some mistakes on this list that are not trivial. However, this post seems to be evidence of what the blog author has learned. It also provides an opportunity from others to learn from his mistakes without having to make the same mistakes themselves.

Predictably, there are some negative feedback comments that do, in effect, ridicule the blog author. Others, however, encourage the blog author for his candor and for describing the types of poor decisions we all see (and in some cases make) fairly often. Perhaps the most interesting facet of the feedback is that some of the acknowledged mistakes are now being debated as to whether they are truly mistakes. I love discussions such as these because, in the end, few things are really as cut and dry as we like to pretend.

UPDATE (7 March 2009): Another refreshingly honest blog posting is How and Why I Missed the Boat as a Developer.

Thứ Hai, 2 tháng 2, 2009

The Java SE 6 NavigableMap

I have written and blogged previously about some of my favorite Java SE 6 features such as the Deque, [Sun's] inclusion of VisualVM, [Sun's] Java HTTP Server, custom JMX MXBeans, String.isEmpty(), and [Sun's] inclusion of JAXB and annotations processing. In this blog posting, I intend to discuss the NavigableMap, a Java Collections interface that I don't use often, but which comes in very handy in certain situations.

The NavigableMap extends the SortedMap and adds methods to this interface specifically designed for easy navigation (hence the name). The next code listing, for the FavoriteMovies class, demonstrates how easy it is to apply the NavigableMap.

FavoriteMovies.java


package dustin.examples.navigable;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Map.Entry;
import java.util.NavigableMap;
import java.util.TreeMap;

/**
* Demonstrate a NavigableMap.
*
* @author Dustin
*/
public class FavoriteMovies
{
/** New line separator. */
private static final String NEW_LINE = System.getProperty("line.separator");

/** Header bar used for separation of output sections. */
private static final String HEADER_BAR =
"=======================================================================";

/**
* Favorite movies with Integer key represent the movie's rating in the
* favorites and the value being the movie itself.
*/
private NavigableMap<Integer, Movie> favoriteMovies =
new TreeMap<Integer, Movie>();

/**
* Add a movie to my favorites.
*
* @param newRanking Ranking of movie being added to favorites.
* @param newMovie Movie being added to favorites.
*/
public void addMovie(
final Integer newRanking, final Movie newMovie)
{
favoriteMovies.put(newRanking, newMovie);
}

/**
* Write a header with the provided headerText to the provided OutputStream.
*
* @param headerText Text to be written in header.
* @param out OutputStream to which to write header.
* @throws java.io.IOException Thrown if header cannot be written to the
* provided OutputStream.
*/
private void printHeader(
final String headerText, final OutputStream out) throws IOException
{
out.write(NEW_LINE.getBytes());
out.write(HEADER_BAR.getBytes());
out.write(NEW_LINE.getBytes());
out.write("= ".getBytes());
out.write(headerText.getBytes());
out.write(NEW_LINE.getBytes());
out.write(HEADER_BAR.getBytes());
out.write(NEW_LINE.getBytes());
}

/**
* Print the contents of my NavigableMap to the provided OutputStream.
*
* @throws IOException Thrown if my contents cannot be written to the
* provided OutputStream.
*/
public void printContents(final OutputStream out) throws IOException
{
printHeader("Contents of Navigable Map", out);
for (Entry<Integer,Movie> favoriteMovie : this.favoriteMovies.entrySet())
{
out.write("Movie Rank #".getBytes());
out.write(String.valueOf(favoriteMovie.getKey()).getBytes());
out.write(": ".getBytes());
out.write(favoriteMovie.getValue().getTitle().getBytes());
out.write(NEW_LINE.getBytes());
}
}

/**
* Write provided label and provided text to provided OutputStream.
*
* @param label Label to be written to OutputStream.
* @param navigableMapText Text to be written to OutputStream.
* @param out OutputStream to which to write label and text.
* @throws java.io.IOException Thrown if exception occurs writing to the
* provided OutputStream.
*/
private void printNavigableMapApproach(
final String label,
final String navigableMapText,
final OutputStream out) throws IOException
{
out.write(label.getBytes());
out.write(": ".getBytes());
out.write(navigableMapText.getBytes());
out.write((NEW_LINE+NEW_LINE).getBytes());
}

/**
* Write the provided Navigable Map to the provided OutputStream with the
* provided label.
*
* @param label Label to be written to OutputStream.
* @param moviesNavigableMap Movies NavigableMap to be written to OutputStream.
* @param out OutputStream to which NavigableMap and its label are to be written.
* @throws java.io.IOException Thrown if exception occurs when writing to the
* provided OutputStream.
*/
private void printMoviesNavigableMap(
final String label,
final NavigableMap<Integer, Movie> moviesNavigableMap,
final OutputStream out) throws IOException
{
out.write(label.getBytes());
out.write(": ".getBytes());
out.write(NEW_LINE.getBytes());
for ( NavigableMap.Entry<Integer,Movie> entry :
moviesNavigableMap.entrySet())
{
out.write("\t#".getBytes());
out.write(entry.getKey().toString().getBytes());
out.write(": ".getBytes());
out.write(entry.getValue().getTitle().getBytes());
out.write(NEW_LINE.getBytes());
}
out.write(NEW_LINE.getBytes());
}

/**
* Write demonstration of several NavigableMap methods to provided
* OutputStream.
*
* @param out OutputStream to which to write output of examples using
* NavigableMap.
* @throws java.io.IOException Thrown if output cannot be written to the
* provided OutputStream.
*/
public void demonstrateNavigableMap(final OutputStream out) throws IOException
{
printHeader("Select NavigableMap Methods", out);

// Demonstrate NavigableMap.firstEntry()
printNavigableMapApproach(
"First Movie [firstEntry()]",
favoriteMovies.firstEntry().getValue().getTitle(),
out);

// Demonstrate NavigableMap.lastEntry()
printNavigableMapApproach(
"Last Movie [lastEntry()]",
favoriteMovies.lastEntry().getValue().getTitle(),
out);

// Demonstrate NavigableMap.floorEntry
printNavigableMapApproach(
"Floor Entry for '6' [floorEntry(K)]",
favoriteMovies.floorEntry(6).getValue().getTitle(),
out);

// Demonstrate NavigableMap.ceilingEntry
printNavigableMapApproach(
"Ceiling Entry for '3' [ceilingEntry(K)]",
favoriteMovies.ceilingEntry(3).getValue().getTitle(),
out);

// Demonstrate NavigableMap.headMap:
// First (lowest) entry automatically assumed; 3 is inclusive (true)
final NavigableMap<Integer, Movie> topMovies =
this.favoriteMovies.headMap(3, true);
printMoviesNavigableMap(
"Top range of movies '1' through '3' [inclusive] - headMap(K,Boolean)",
topMovies,
out);

// Demonstrate NavigableMap.subMap:
// Make lower (from) number inclusive (true) and higher (to) number
// exclusive (false).
final NavigableMap<Integer, Movie> middleMovies =
this.favoriteMovies.subMap(4, true, 7, false);
printMoviesNavigableMap(
"Middle range of movies '4' through '6' [7 not inclusive] - subMap"
+ "(K,Boolean,K,Boolean)",
middleMovies,
out);

// Demonstrate NavigableMap.tailMap:
// Last (highest) entry automatically assumed; 6 is exclusive (false)
final NavigableMap<Integer, Movie> bottomOfBestMovies =
this.favoriteMovies.tailMap(6, false);
printMoviesNavigableMap(
"Bottom range of best movies '7' through '10' [6 not inclusive] "
+ "- tailMap(K,Boolean)",
bottomOfBestMovies,
out);
}

/**
* Set up an instance of me with pre-populated data.
*
* @return Instance of me with pre-populated data (for testing and
* demonstration of this class).
*/
public static FavoriteMovies setUpFavoriteMovies()
{
final FavoriteMovies movies = new FavoriteMovies();
movies.addMovie(
3,
new Movie.Builder().title("Raiders of the Lost Ark")
.genre(MovieGenre.FANTASY)
.build());
movies.addMovie(
2,
new Movie.Builder().title("Star Wars: The Empire Strikes Back")
.genre(MovieGenre.SCIENCE_FICTION)
.build());
movies.addMovie(
4,
new Movie.Builder().title("Men in Black")
.genre(MovieGenre.SCIENCE_FICTION)
.build());
movies.addMovie(
1,
new Movie.Builder().title("Fletch")
.genre(MovieGenre.COMEDY)
.build());
movies.addMovie(
5,
new Movie.Builder().title("Ocean's Eleven")
.genre(MovieGenre.ACTION)
.build());
movies.addMovie(
9,
new Movie.Builder().title("The Outlaw Josey Wales")
.genre(MovieGenre.WESTERN)
.build());
movies.addMovie(
8,
new Movie.Builder().title("Groundhog Day")
.genre(MovieGenre.COMEDY)
.build());
movies.addMovie(
10,
new Movie.Builder().title("The Sixth Sense")
.genre(MovieGenre.HORROR)
.build());
movies.addMovie(
7,
new Movie.Builder().title("War Games")
.genre(MovieGenre.SCIENCE_FICTION)
.build());
movies.addMovie(
6,
new Movie.Builder().title("The Princess Bride")
.genre(MovieGenre.COMEDY)
.build());
return movies;
}

/**
* Main executable to demonstrate NavigableMap.
*
* @param arguments Command-line arguments; none anticipated.
*/
public static void main(final String[] arguments)
{
final FavoriteMovies movies = setUpFavoriteMovies();
try
{
movies.printContents(System.out);
movies.demonstrateNavigableMap(System.out);
}
catch (Exception ex)
{
System.err.println("Exception encountered: " + ex.toString());
}
}
}



The above code populates a TreeMap implementation of NavigableMap with information about some of my favorite movies and then invokes several methods on that interface to demonstrate the NavigableMap.

When the above class is run, the output appears as shown next:



The output screen snapshot shows how several significant NavigableMap methods work. It also demonstrates the automatic sorting supported by the NavigableMap as a specialized SortedMap: the values were put into the TreeMap intentionally out of order, but are printed in order when the NavigableMap is traversed using the for-each loop.

For completeness, I include the simple code listings for the Movie class and for the MovieGenre class.

Movie.java


package dustin.examples.navigable;

/**
* Class representating a movie.
*
* @author Dustin
*/
public class Movie
{
/** Title of the movie. */
private String title;

/** Year of movie's release. */
private int year;

/** Movie's primary director. */
private String director;

/** Movie's primary genre. */
private MovieGenre genre;

/** No-arguments constructor not intended for public consumption. */
private Movie() {}

/**
* Provide my director.
*
* @return My director.
*/
public String getDirector()
{
return this.director;
}

/**
* Provide my genre.
*
* @return My genre.
*/
public MovieGenre getGenre()
{
return this.genre;
}

/**
* Provide my title.
*
* @return My title.
*/
public String getTitle()
{
return this.title;
}

/**
* Provide my rear of release.
*
* @return The year of my release.
*/
public int getYear()
{
return this.year;
}

/**
* Provide a String representation of me.
*
* @return My String representation.
*/
@Override
public String toString()
{
final StringBuilder builder = new StringBuilder();
builder.append("Title: ").append(this.title);
builder.append("; Year: ").append(this.year);
builder.append("; Director: ").append(this.director);
builder.append("; Genre: ").append(this.genre);
return builder.toString();
}

public static class Builder
{
private String title;
private int year;
private String director;
private MovieGenre genre;

public Builder() {}

public Builder director(final String newDirector)
{
this.director = newDirector;
return this;
}

public Builder genre(final MovieGenre newGenre)
{
this.genre = newGenre;
return this;
}

public Builder title(final String newTitle)
{
this.title = newTitle;
return this;
}

public Builder year(int newYear)
{
this.year = newYear;
return this;
}

public Movie build()
{
return new Movie(this);
}
}

/**
* Constructor intended to be used in conjunction with Builder.
*
* @param builder Builder for building an instance of me.
*/
private Movie(final Builder builder)
{
this.title = builder.title;
this.director = builder.director;
this.year = builder.year;
this.genre = builder.genre;
}
}



MovieGenre.java


package dustin.examples.navigable;

/**
* Represent a movie genre.
*
* @author Dustin
*/
public enum MovieGenre
{
ACTION,
COMEDY,
DRAMA,
FANTASY,
HORROR,
ROMANTIC_COMEDY,
SCIENCE_FICTION,
WESTERN
}



Conclusion

The Java SE 6 NavigableMap is not something I use on a daily basis, but it has come in handy now and then. It is easy to apply and, in the appropriate situations, makes it really easy to navigate a Map. Although not covered here, there is also a NavigableSet that does for the Set what NavigableMap does for the Map. In fact, the NavigableMap.navigableKeySet() method returns a NavigableSet.

In honor of today being Groundhog Day, it is time to watch the eighth movie on today's list: Groundhog Day.