To illustrate, I will show a couple examples of scripts that can be supported directly with Java classes, but with are much more concise and readable when written in Groovy. For each example, I'll first show the normal Java example followed by the Groovy equivalent. Along the way, I'll point out the Groovy features that make Groovy so script friendly.
Script Example #1: Use of InetAddress
There are several times when it is useful to know information about the network one is using as well as about individual hosts on that network. A particularly useful class in such situations is the java.net.InetAddress class. I have benefited from this class repeatedly when working with JMX on Linux. In particular, there is a known issue (occasionally) with Linux when a machine's loopback address is not configured to support
localhost
so that hostname -i
produces an IP address other than 127.0.0.1.Although there is an operating system command to look this information, there are some advantages to using a Java-based approach. First, when working with Java libraries and toolkits, it is often nice to use the same mechanisms they use in scripts to increase the likelihood of seeing similar results. Second, an operating system specific command does not, by its very nature, necessarily cross operating systems well. A Java-based script will generally run on any operating system for which a JVM is available.
I have blogged before about using InetAddress. My first regular Java example is based on the example provided in that blog post. Because there are some minor changes and for convenience, a slightly modified version of that example is shown here.
package dustin.examples;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* This class demonstrates the InetAddress class.
*/
public class InetAddressDemonstrator
{
/**
* Run simple demonstration of the usefulness of the java.net.InetAddress
* class.
*
* @param aArgs The command line arguments; none expected.
*/
public static void main(final String[] aArgs)
{
InetAddress localhost = null;
try
{
localhost = InetAddress.getLocalHost();
System.out.println( "InetAddress: " + localhost );
System.out.println( "\ttoString: " + localhost.toString() );
System.out.println( "\tCanonicalHostName: "
+ localhost.getCanonicalHostName() );
System.out.println( "\tHost Name: " + localhost.getHostName() );
System.out.println( "\tHost Address: " + localhost.getHostAddress() );
System.out.println( "\tHost Bytes: " + localhost.getAddress() );
System.out.println( "\tHash Code: " + localhost.hashCode() );
}
catch (UnknownHostException unknownHostException) // checked exception
{
System.err.println( "Doh!! Unknown Host ("
+ unknownHostException.getClass().toString()
+ "): " + unknownHostException.getMessage() );
}
}
}
Besides the code itself that accesses
InetAddress
to provide information about the local host, additional code that is required for this example includes the two import statements, the class declaration code, the main function for the executable class, and the try-catch blocks for handling the checked exception java.net.UnknownHostException (different than java.rmi.UnknownHostException).The output of running the above code looks like that shown in the next screen snapshot.
The relative "ceremony" code to actually desired executable code is even worse if only one call to an InetAddress instance is required. This is shown in the next code listing for a class that simply returns the localhost information without all the other details provided in the earlier code example.
package dustin.examples;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* This class demonstrates the InetAddress class.
*/
public class InetAddressDemonstrator2
{
/**
* Run simple demonstration of the usefulness of the java.net.InetAddress
* class.
*
* @param aArgs The command line arguments; none expected.
*/
public static void main(final String[] aArgs)
{
try
{
System.out.println( "InetAddress: " + InetAddress.getLocalHost() );
}
catch (UnknownHostException unknownHostException) // checked exception
{
System.err.println( "Doh!! Unknown Host ("
+ unknownHostException.getClass().toString()
+ "): " + unknownHostException.getMessage() );
}
}
}
The output from this simplified Java code is shown in the next screen snapshot.
Even with only one call on
InetAddress
, there were still quite a few more lines of code required to use it. The same functionality is implemented in Groovy as shown in the next code listing.getLocalHost.groovy
println "InetAddress: " + InetAddress.getLocalHost()
If you blinked while reading the above or started to wander a little, you probably missed the single line of Groovy code. That's the entire script. Assuming that Groovy has been installed correctly and that the system path includes the Groovy installation
bin
directory, all that is needed to run this script is groovy getLocalHost
. The output is shown in the next screen snapshot.This output shows that the results of running the Java code and the single line Groovy script are the same (not surprising given that Groovy really IS Java). The Groovy code is so short because no imports were required (Groovy does not require any class in a package starting with
java
to be imported), no checked exception had to be caught (Groovy does not force checking or handling of exceptions), and no class and main method declarations are required in Groovy. These base assumptions in Groovy significantly reduce the overhead associated with running this single statement. Even running the Groovy script happened to be slightly easier in this example because no classpath had to be specified for the Groovy script in this particular case.If we wanted all the information to be displayed from a Groovy script that was shown in the very first Java code example, the Groovy script remains simple:
useInetAddress.groovy
localhost = InetAddress.getLocalHost()
println "InetAddress: " + localhost
println "\ttoString: " + localhost.toString()
println "\tCanonicalHostName: " + localhost.getCanonicalHostName()
println "\tHost Name: " + localhost.getHostName()
println "\tHost Address: " + localhost.getHostAddress()
println "\tHost Bytes: " + localhost.getAddress()
println "\tHash Code: " + localhost.hashCode()
The output of this Groovy script is the same as for the Java class as demonstrated in the next screen snapshot.
In this example, I included the
.groovy
extension on the name of the Groovy script file being run to make it a little different than the previous example.Script Example #2: Readable Java Epoch Time
When working with Date and Calendar and other related contexts, it is common to store and pass around dates as longs representing the number of milliseconds since Java epoch. It can be handy to have scripts that allow for easier use of these long representations of a date/time. For example, it can be helpful to know what long corresponds to the current date/time and to convert a given long to its corresponding date/time in Java. This is especially helpful when reviewing logs or other code output that displays dates in this milliseconds since epoch form.
The next code listing is simple Java code for displaying the current time in terms of milliseconds since Java's epoch time.
package dustin.examples;
public class TimeNowInMillisecondsSinceEpochTime
{
/**
* Provide current time as milliseconds since Java epoch time.
*
* @param arguments Command-line arguments; none expected.
*/
public static void main(final String[] arguments)
{
System.out.println( "Milliseconds Since Java Epoch: "
+ System.currentTimeMillis());
}
}
The output from this sample Java code is shown next.
The Groovy code for the same functionality is even simpler and again fits into a single line:
println "Milliseconds Since Java Epoch: " + System.currentTimeMillis()
The output is the same as for Java (with some milliseconds passed since running that example):
There are numerous times when I need to know what a given long representing milliseconds since Java epoch time is in terms I better understand. A simple Java class that can convert milliseconds to human readable date/time is shown next.
package dustin.examples;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;
/**
* This class converts the provided milliseconds (as sole command-line argument)
* to a Calendar. This class assumes a US/Denver/Mountain time zone and English
* locale.
*/
public class MillisecondsSinceEpochConverter
{
/**
* Main executable for this class. Expects a single command-line argument,
* which is assumed to be a Long that represents a time based on milliseconds
* since Java epoch time.
*
* @param arguments Command-line arguments: one expected, the long that
* represents the number of milliseconds since Java epoch for which the
* more human readable date/time String is desired.
*/
public static void main(final String[] arguments)
{
final Long millisecondsSinceEpoch = Long.valueOf(arguments[0]);
Calendar calendar =
Calendar.getInstance(TimeZone.getTimeZone("America/Denver"), Locale.US);
calendar.setTimeInMillis(millisecondsSinceEpoch);
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(
millisecondsSinceEpoch + " is "
+ dateFormat.format(calendar.getTime())
+ " (" + calendar.getTimeZone().getDisplayName() + ")");
}
}
Output from running the above Java code is shown next.
The Groovy equivalent to the above is now shown.
getDateTimeRepresentation.groovy
import java.text.SimpleDateFormat
final Long millisecondsSinceEpoch = Long.valueOf(args[0])
calendar = Calendar.getInstance(TimeZone.getTimeZone("America/Denver"), Locale.US)
calendar.setTimeInMillis(millisecondsSinceEpoch)
dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
println millisecondsSinceEpoch + " is " + dateFormat.format(calendar.getTime()) + " (" + calendar.getTimeZone().getDisplayName() + ")"
The output from running the Groovy script is shown next.
In all of the Groovy examples so far, including this last one, I left off the static data typing to show off Groovy's duck typing. I did have to import one Java class in the last example, but I was able to remove the rest of the import statements. I also had to use the name "args" for the command-line arguments in Groovy because there was no explicit specification of how the command-line arguments would be accessed. Instead, Groovy implicitly provides the command-line arguments in an array of Strings called "args."
I also placed the entire last statement that outputs the results to standard output on a single line. If I had left the statement spanning multiple lines as it was in the Java version, I'd see an error message saying something like
Caught: org.codehaus.groovy.runtime.metaclass.MissingMethodExceptionNoStack: No signature of method: java.lang.String.positive() is applicable for argument types: () values: []
This same error is also shown in the next screen snapshot.
However, there is an even better way to output this final String to the standard output. Instead of using the overridden
+
operator to add Strings, I can take advantage of Groovy's GString support. The revised version of the Groovy script is shown next (only the last statement has changed).
import java.text.SimpleDateFormat
final Long millisecondsSinceEpoch = Long.valueOf(args[0])
calendar = Calendar.getInstance(TimeZone.getTimeZone("America/Denver"), Locale.US)
calendar.setTimeInMillis(millisecondsSinceEpoch)
dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
println "${millisecondsSinceEpoch} is ${dateFormat.format(calendar.getTime())} (${calendar.getTimeZone().getDisplayName()})"
There is one more Groovy feature to demonstrate in this example. The last statement is still on one line. Groovy supports multiline Strings. These are specified by enclosing the multi-line Strings between three double quotes markers and by using backslashes at the end of each code line that should NOT be a newline in the String. The final revision of this long-to-readable Date/Time representation script is shown next.
import java.text.SimpleDateFormat
final Long millisecondsSinceEpoch = Long.valueOf(args[0])
calendar = Calendar.getInstance(TimeZone.getTimeZone("America/Denver"), Locale.US)
calendar.setTimeInMillis(millisecondsSinceEpoch)
dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
println """${millisecondsSinceEpoch} is ${dateFormat.format(calendar.getTime())} \
(${calendar.getTimeZone().getDisplayName()})"""
The output for all three versions of this Groovy script are exactly the same.
Script Example #3: System Properties
I often want to know which Java System properties are defined for my environment. The following Java code will provide this information.
package dustin.examples;
import java.util.Enumeration;
import java.util.Properties;
/**
* Class that accesses the System properties.
*/
public class SystemPropertiesAccessor
{
public static void main(final String[] arguments)
{
Properties systemProperties = System.getProperties();
Enumeration propertyKeys = systemProperties.keys();
while (propertyKeys.hasMoreElements())
{
String key = (String) propertyKeys.nextElement();
String value = (String) systemProperties.get(key);
System.out.println(key + "=" + value);
}
}
}
I will not show the output here, but it consists of name/value pairs for the system properties with an equals sign separating the name and value. The equivalent Groovy code is shown next.
systemProperties.groovy
theSystemProperties = System.getProperties();
propertyKeys = theSystemProperties.keys();
while (propertyKeys.hasMoreElements())
{
key = propertyKeys.nextElement();
value = theSystemProperties.get(key);
println "${key}=${value}"
}
The Groovy version loses the casts to String along with no more need for static data typing and the removal of import statements. The output statement uses the same
${}
syntax we looked at in the last Groovy example.Another interesting observation can be made from this last Groovy example. Because I named the Groovy script file
systemProperties.groovy
, I was not able to name my variable the same thing (it was named systemProperties
in the Java code). The name conflict is evident in the associated error message.Conclusion
Groovy allows Java developers to write tight, concise scripts that combine the advantages of scripting languages with the advantage of access to the large set of Java classes and libraries. Groovy allows the script writer to adapt a Java class minimally or to move more fully to Groovy-specific syntax as feels appropriate for the given situation.
5 comments:
Why you are comparing java with comments and exception handling to groovy with none of it ? At least try to be bit honest in comparison - make main method 'throws Exception', delete all comments, use import with .*; Java will be still longer than groovy, but you will be at least comparing same code.
Nice post. Actually, most of the Groovy snippets you published in this post can be easily made more-Groovy and even more concise. E.g. the snippet printing out system properties can be written as such:
System.getProperties().each { key, value ->
println "$key: $value"
}
Michal,
Thanks for pointing that out. One of them things I really like about Groovy is the ability to use everything from nearly 100% "traditional" Java to 100% Groovy to anywhere between.
abies,
Thanks for the feedback. It would be a more fair comparison to make those changes you suggest if the only consideration is lines of code. I find in my case that I actually write code differently depending on whether I am writing a script of an application. In application development, for which I traditionally use 'traditional' Java, I do use comments, explicit imports, and try not to require clients to handle checked exceptions. With scripting, however, I tend to want very concise syntax. I like Groovy because I like Java, but I definitely write Groovy scripts differently than I write Java applications.
Actually, the properties example is not even semantically equivalent - for instance the groovy example given does not have a containing class and without modification would not be accessible outside this scope.
There is nothing cleaner here imo, really your just discarding context for brevity.
If demonstrating the advantage of Groovy's syntactic sugar is your goal then using closures and built in collection syntax would be the way to go.
Interesting read though Dustin, thanks!
Post a Comment