Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Kenneth A. Kousen - Making Java Groovy - 2014.pdf
Скачиваний:
50
Добавлен:
19.03.2016
Размер:
15.36 Mб
Скачать

File I/O

315

if (employee == null) { loc = null;

} else {

Department dept = employee.getDepartment(); if (dept == null) {

loc = null; } else {

loc = dept.getLocation();

}

}

That’s quite an expansion just to check for nulls. Here’s the Groovy version:

Location loc = employee?.department?.location

The safe de-reference operator returns null if the reference is null. Otherwise it proceeds to access the property. It’s a small thing, but the savings in lines of code is nontrivial.

Continuing on the theme of simplifying code over the Java version, consider input/output streams. Groovy introduces several methods in the Groovy JDK that help Groovy simplify Java code when dealing with files and directories.

B.7 File I/O

File I/O in Groovy isn’t fundamentally different from the Java approach. Groovy adds several convenience methods and handles issues like closing your files for you. A few short examples should suffice to give you a sense of what’s possible.

First, Groovy adds a getText method to File, which means that by asking for the text property you can retrieve all the data out of a file at once in the form of a string:

String data = new File('data.txt').text

Accessing the text property invokes the getText method, as usual, and returns all the text in the file. Alternatively, you can retrieve all the lines in the file and store them in a list using the readLines method:

List<String> lines = new File("data.txt").readLines()*.trim()

The trim method is used in this example with the spread-dot operator to remove leading and trailing spaces on each line. If your data is formatted in a specific way, the splitEachLine method takes a delimiter and returns a list of the elements. For example, if you have a data file that contains the following lines

1,2,3

a,b,c

then the data can be retrieved and parsed at the same time:

List dataLines = []

new File("data.txt").splitEachLine(',') { dataLines << it

}

assert dataLines == [['1','2','3'],['a','b','c']]

www.it-ebooks.info

316

APPENDIX B Groovy by feature

Writing to a file is just as easy:

File f = new File("$base/output.dat") f.write('Hello, Groovy!')

assert f.text == 'Hello, Groovy!'

In Java, it’s critical to close a file if you’ve written to it, because otherwise it may not flush the buffer and your data may never make into the file. Groovy does that for you automatically.

Groovy also makes it easy to append to a file:

File temp = new File("temp.txt") temp.write 'Groovy Kind of Love' assert temp.readLines().size() == 1

temp.append "\nGroovin', on a Sunday afternoon..." temp << "\nFeelin' Groovy"

assert temp.readLines().size() == 3 temp.delete()

The append method does what it sounds like, and the left-shift operator has been overridden to do the same.

Several methods are available that iterate over files, like eachFile, eachDir, and even eachFileRecurse. They each take closures that can filter what you want.

Finally, I have to show you an example that illustrates how much simpler Groovy I/O streams are than Java streams. Consider writing a trivial application that does the following:

1Prompts the user to enter numbers on a line, separated by spaces

2Reads the line

3Adds up the numbers

4Prints the result

Nothing to it, right? The next listing shows the Java version.

Listing B.4 SumNumbers.java, an application to read a line of numbers and add them

package io;

 

 

 

 

import java.io.BufferedReader;

 

 

 

 

import java.io.IOException;

 

 

 

 

import java.io.InputStreamReader;

 

Must be

 

 

 

public class SumNumbers {

 

in a class

 

public static void main(String[] args) {

 

 

 

Has readLine

 

 

 

System.out.println("Please enter numbers to sum");

method

BufferedReader br =

 

 

 

 

 

 

 

 

new BufferedReader(new InputStreamReader(System.in));

String line = "";

 

 

 

 

try {

 

 

 

 

line = br.readLine();

 

 

try/catch for

 

} catch (IOException e) {

 

 

 

checked exception

 

e.printStackTrace();

 

 

 

 

 

 

 

}

 

 

 

 

 

 

www.it-ebooks.info

XML

String[] inputs = line.split(" "); double total = 0.0;

for (String s : inputs) {

total += Double.parseDouble(s);

}

System.out.println("The sum is " + total);

}

}

317

Convert strings to doubles

That’s nearly 30 lines to do something extremely simple. All Java code has to be in a class with a main method. The input stream System.in is available, but I want to read a full line of data, so I wrap the stream in an InputStreamReader and wrap that in a BufferedReader, all so I can call readLine. That may throw an I/O exception, so I need a try/catch block for it. Finally, the incoming data is in string form, so I need to parse it before adding up the numbers and printing the results.

Here’s the corresponding Groovy version:

println 'Please enter some numbers' System.in.withReader { br ->

println br.readLine().tokenize()*.toBigDecimal().sum()

}

That’s the whole program. The withReader method creates a Reader implementation that has a readLine method and automatically closes it when the closure completes. Several similar methods are available for both input and output, including withReader, withInputStream, withPrintWriter, and withWriterAppend.

That was fun, but here’s another version that has more capabilities. In this case, the code has a loop that sums each line and prints its result until no input is given:

println 'Sum numbers with looping' System.in.eachLine { line ->

if (!line) System.exit(0)

println line.split(' ')*.toBigDecimal().sum()

}

The eachLine method repeats the closure until the line variable is empty.

Groovy’s contribution to file I/O is to add convenience methods that simplify the Java API and ensure that streams or files are closed correctly. It provides a clean façade on the Java I/O package.

Groovy makes input/output streams much simpler to deal with than in Java, so if I have a Java system and I need to work with files, I try to add a Groovy module for that purpose. That’s a savings, but nothing compared to the savings that result from using Groovy over Java when dealing with XML, as shown in the next section.

B.8 XML

I’ve saved the best for last. XML is where the ease-of-use gap between Groovy and Java is the largest. Working with XML in Java is a pain at best, while parsing and generating XML in Groovy is almost trivial. If I ever have to deal with XML in a Java system, I always add a Groovy module for that purpose. This section is intended to show why.

www.it-ebooks.info

318

APPENDIX B Groovy by feature

B.8.1 Parsing and slurping XML

Some time ago, I was teaching a training course on XML and Java. One of the exercises started by presenting an XML file similar to this one:

<books>

<book isbn="9781935182443">

<title>Groovy in Action (2nd edition)</title> <author>Dierk Koenig</author> <author>Guillaume Laforge</author> <author>Paul King</author>

<author>Jon Skeet</author> <author>Hamlet D'Arcy</author>

</book>

<book isbn="9781935182948"> <title>Making Java Groovy</title> <author>Ken Kousen</author>

</book>

<book isbn="1933988932"> <title>Grails in Action</title> <author>Glen Smith</author> <author>Peter Ledbrook</author>

</book>

</books>

The goal of the exercise was to parse this file and print out the title of the second book. Because this file is small, you might as well use a DOM parser to read it. To do that in Java you need a factory, which then yields the parser, and then you can invoke a parse method to build the DOM tree. Then, to extract the data, there are three options:

Walk the tree by getting child elements and iterating over them.

Use the getElementById method to find the right node, and then get the first text child and retrieve its value.

Use the getElementsByTagName method, iterate over the resulting NodeList to find the right node, and then retrieve the value of the first text child.

The first approach runs into problems with whitespace. This document has carriage returns and tabs in it, and because no DTD or schema is provided, the parser doesn’t know which whitespace elements are significant. Traversing the DOM is complicated by the fact that methods like getFirstChild will return whitespace nodes as well as elements. It can be done, but you’ll need to check the node type of each element to make sure you are working with an element rather than a text node.

The second approach only works if the elements have an attribute of type ID, and that’s not the case here.

You’re left with the getElementsByTagName method, which results in the following code:

import java.io.IOException;

import javax.xml.parsers.DocumentBuilder;

import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException;

www.it-ebooks.info

XML

319

import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.SAXException;

public class ProcessBooks {

public static void main(String[] args) { DocumentBuilderFactory factory =

DocumentBuilderFactory.newInstance(); Document doc = null;

try {

DocumentBuilder builder = factory.newDocumentBuilder(); doc = builder.parse("books.xml");

}catch (ParserConfigurationException e) { e.printStackTrace();

}catch (SAXException e) { e.printStackTrace();

}catch (IOException e) {

e.printStackTrace();

}

NodeList titles = doc.getElementsByTagName("title"); Element titleNode = (Element) titles.item(1);

String title = titleNode.getFirstChild().getNodeValue(); System.out.println("The second title is " + title);

}

}

Parsing the document can throw all sorts of exceptions, as shown. Assuming nothing goes wrong, after parsing, the code retrieves all the title elements. After getting the proper element out of the NodeList and casting it to type Element, you then have to remember that the character data in the element is in the first text child of the element rather than the element itself.

Here’s the Groovy solution:

root = new XmlSlurper().parse('books.xml')

assert root.book[1].title == 'Making Java Groovy'

Wow. Groovy includes the XmlSlurper class, which is in the groovy.util package (no import required). XmlSlurper has a parse method that builds the DOM tree and returns the root element. Then it’s a question of walking the tree, using the dot notation for child elements. Elements that appear multiple times form a collection that can be accessed with an index in the normal way. The contrast in both size and complexity between the Groovy version and the Java version is clear.

The next listing demonstrates working with the XML file.

Listing B.5 Slurping XML

String fileName = 'books.xml'

def books = new XmlSlurper().parse(fileName)

assert books.book.size() == 4

assert books.book[0].title == "Groovy in Action"

www.it-ebooks.info

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]