Apache Groovy is a powerful, concise and expressive language that can be used for a wide variety of scripting tasks. This article series aims to show more of what it can do for you. (If you haven’t installed Groovy yet, please read the intro.)
To get started, write a command-line program in Java to copy a file to standard output:
1 import java.io.File;
2 import java.io.FileReader;
3 import java.io.BufferedReader;
4 import java.io.FileNotFoundException;
5 import java.io.IOException;
6 public class Groovy01 {
7 public static void main(String[] args) {
8 if (args.length != 1) {
9 System.err.println("Usage: java Groovy01 input-file-name");
10 System.exit(0);
11 }
12 File inputFile = new File(args[0]);
13 try {
14 BufferedReader reader = new BufferedReader(new FileReader(inputFile));
15 String inputLine;
16 while ((inputLine = reader.readLine()) != null) {
17 System.out.println(inputLine);
18 }
19 reader.close();
20 } catch (FileNotFoundException fnfe) {
21 System.out.println("FileNotFound exception");
22 fnfe.printStackTrace();
23 } catch (IOException ioe) {
24 System.out.println("IO exception");
25 ioe.printStackTrace();
26 }
27 }
28 }
This is a pretty straightforward Java program:
- Lines one to five import the various Java classes needed to carry out the task.
- Lines six-28 define the main class
Groovy01
(Java programs are always a class). - Lines seven-27 define the main method (Java programs always provide a
main()
method which is called by the operating system and carries out the intended work of the program. This is a standard inherited from the C programming language and Unix back in the day). - Lines eight-11 make sure an argument is provided, which is intended to be the file to be read.
- Lines 12-26 contain the main reading-writing logic and provide
try
–catch
error handling required by theFileReader
constructor (catchingFileNotFoundException
) and theBufferedReader
constructor (catchingIOException
). - Lines 15-18 are the lines that do the file copying, line by line.
Reading the lines of a file in a script can be a lot of work, because it requires setting up a lot of stuff and figuring out how to use it. In fact, the only line in the script that does not directly relate to reading the file is line 17:
17 System.out.println(inputLine);
Here’s how I do this in Groovy:
1 if (args.length != 1) {
2 System.err.println "Usage: groovy Groovy01.groovy input-file"
3 System.exit(0)
4 }
5 new File(args[0]).withReader { reader ->
6 reader.eachLine { line ->
7 println line
8 }
9 }
Clearly, if you use a similar Groovy “script” you have a lot less to worry about!
- Lines one to four ensure that one argument is provided.
- Lines five to nine contain the main reading-writing logic.
If you start at the beginning, there are often no import statements required. There is no main class that must be defined. There is no main()
method that must be defined. From the other end, there are no try
– catch
error handling steps required. All of these are optional — if the programmer doesn’t include them, reasonable defaults are included “stealthily.” If the user of the script provides an argument that cannot be interpreted as a readable file, the script will fail with the same kind of FileNotFoundException
or IOException
as in the case of the Java program. Some Groovy programmers are particularly happy with this as the try
– catch
handling they typically implement simply dumps out the exception information anyway.
Note the withReader { reader -> … }
and eachLine { line -> … }
constructs, are both method calls, whose last argument is an instance of the Groovy class Closure
.
Groovy closures are anonymous blocks of code that can take arguments, interact with the containing scope and return values. They are similar to Java lambdas but have some advantages, especially the ability to interact with the containing scope. Because closures have been in Groovy since the beginning, many classes provide methods that work with closures, and the language provides syntactic support for closures.
Taking the simplest example:reader
has an eachLine()
method that loops over each line in the input defined by the reader and calls its closure argument. It passes the line read as an argument to that closure. In this case, the body of the closure just prints out the line on standard output. But much more complicated things could happen. For example, if the goal were to count the number of lines and the total number of characters, lines five to nine might be replaced with:
5 int nLines = 0, nChars = 0
6 new File(args[0]).withReader { reader ->
7 reader.eachLine { line ->
8 nLines++
9 nChars += line.size()
10 }
11 }
12 println "# of lines = $nLines # of chars = $nChars"
Here you see the line and character counters defined in line five, accumulated in lines eight and nine and printed at the end of the file in line 12.
A nice thing that happens in the withReader()
method is that the input stream is automatically closed at exit.
Conclusion
In this series, I won’t always compare the Groovy and Java equivalents and bang the drum for Groovy’s concise nature. But in this case it makes sense, since I’m talking about a kind of “boiler plate” for scripting. In the case of the kind of tasks that require reading a text file with line breaks and doing something with the information on each line, you can see that the “boiler plate” would look something like this:
1 if (args.length != 1) {
2 System.err.println "Usage: groovy Groovy01.groovy input-file"
3 System.exit(0)
4 }
5 // set up any accumulators
6 new File(args[0]).withReader { reader ->
7 reader.eachLine { line ->
8 // analyze the line and accumulate stuff
9 }
10 }
11 // summarize the accumulators
This is what makes scripting in Groovy so wonderful — just plug in the relevant task details in lines five, eight and 11 and away you go.
This post is part of a series on Apache Groovy, stay tuned for more.
Photo by Patrick Fore on Unsplash