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

56

CHAPTER 3 Code-level integration

In the next section I want to relax that requirement. If you’re willing to use some classes from the Groovy standard library, life gets simpler.

3.2.2Working with the Groovy Eval class

There are two special classes in the Groovy library, groovy.util.Eval and groovy

.lang.GroovyShell, specifically designed for executing scripts. In this section I’ll show examples using the Eval class, and in the next section I’ll show GroovyShell. In each case, the goal is still to invoke external Groovy scripts from Java.

The Eval class is a utility class (all its methods are static) for executing operations that take none, one, two, or three parameters. The relevant methods are shown in table 3.1.

Table 3.1 Static methods in groovy.util.Eval for executing Groovy from Java

Eval.me Overloaded to take a String expression or an expression with a String symbol and an Object

Eval.x One argument: the value of x

Eval.xy Two arguments, x and y

Eval.xyz Three arguments, x, y, and z

To demonstrate the methods I’ll add additional tests to the JUnit test case. The test is written in Java, so I’ll automatically call Groovy from Java.

The following listing shows four tests, one for each of the static methods in the Eval class.

Listing 3.4 JUnit 4 test class verifying results of calling Eval methods from Java

public class ScriptingTests { @Test

public void testEvalNoParams() {

String result = (String) Eval.me("'abc' - 'b'"); assertEquals("ac",result);

}

@Test

public void testEvalOneParam() {

String result = (String) Eval.x("a", "'abc' - x"); assertEquals("bc",result);

}

Zero-argument me method

One-argument x method

@Test

Two-argument

xy method

public void testEvalTwoParams() {

 

 

String result = (String) Eval.xy("a", "b", "'abc' - x - y");

 

 

assertEquals("c",result);

 

 

}

 

 

@Test

Three-argument

public void testEvalThreeParams() {

xyz method

String result =

 

 

(String) Eval.xyz("a", "b", "d", "'abc' - x - y + z");

www.it-ebooks.info

Executing Groovy scripts from Java

57

assertEquals("cd",result);

}

}

In each test the Groovy script to be evaluated is included as a string. Unlike the ScriptEngine there’s no overload for instances of Reader, so to execute a script in a separate file would require reading the file into a string. The methods also assume that the input variables are called x, y, and z, which might be asking too much. Still, it’s interesting that this mechanism exists at all.

In addition to illustrating the mechanics of calling Groovy scripts from Java, the tests also demonstrate operator overloading in the String class. The minus operator in Groovy corresponds to the minus method in String. Its implementation to remove the first instance of its argument from the given string is used with strings to remove instances of substrings. In Groovy, strings can be contained within either single or double quotes. Single-quoted strings are regular Java strings, and double-quoted strings are parameterized strings, diplomatically called Groovy strings, but formally called, unfortunately, GStrings.6

The process of using Eval from Java is shown in figure 3.4.

 

 

 

 

 

me, x,

 

Args

 

 

 

xy, xyz

 

Java class

 

 

eval (Groovy)

 

 

 

Script in Groovy

 

 

 

 

 

 

 

 

 

 

 

 

 

Figure 3.4 Java calls the me, x, xy, or xyz method in the Groovy Eval class to execute a script.

The Eval class is convenient and simple, but often it’s too simple. It rests on a more powerful foundation, the GroovyShell class, which I’ll discuss next.

3.2.3Working with the GroovyShell class

The GroovyShell class is used for scripts that aren’t restricted to the special cases described in the previous section on Eval. The class groovy.lang.GroovyShell can be used to execute scripts, particularly when combined with a Binding.

Unlike Eval, the GroovyShell class does not contain only static methods. It needs to be instantiated before invoking its evaluate method. As a simple example, consider adding the following test to the previous set of test cases:

@Test

public void testEvaluateString() { GroovyShell shell = new GroovyShell();

6To make matters worse, simple parameters are injected into GStrings using a dollar sign. This has led to far too many “insert a $ into a GString” jokes. To me, this is a clear demonstration that we don’t have enough women in computer science. Don’t you think that if there had been one woman on the team at the time, she could have said, “Hey, that’s a funny joke, but let’s not build it into the standard library that’s going to be used by everybody forever?” After all, it’s hard enough to get a language named Groovy taken seriously by the Fortune 500 without going there, too. For my part, I call them Groovy strings, which is what the class should have been called all along. It is a funny joke, though—for about 10 minutes.

www.it-ebooks.info

58

CHAPTER 3 Code-level integration

Object result = shell.evaluate("3+4"); assertEquals(7, result);

}

The evaluate method is heavily overloaded. The version I’m using here takes a string representing the script to be evaluated. Other overloads take a java.io.File or a java.io.Reader instance, with various additional arguments. There are overloads that take a java.io.InputStream as well, but they’re deprecated due to possible encoding issues.

So far, using the GroovyShell looks a lot like using the ScriptEngine class, though you can instantiate it directly in this case. To deal with input and output variables, however, the GroovyShell uses the groovy.lang.Binding class to provide a map of input and output variables.

The next listing shows the Binding and GroovyShell classes in action. It’s another test to add to the growing JUnit 4 test case.

Listing 3.5 Using GroovyShell and Binding to invoke the Google geocoder

Use binding in GroovyShell

@Test

public void testLatLng() {

Binding binding = new Binding();

Create and

binding.setVariable("street", "Blackheath Avenue");

populate the

binding.setVariable("city", "Greenwich");

binding

binding.setVariable("state", "UK");

 

 

 

GroovyShell shell = new GroovyShell(binding);

 

 

 

try {

 

 

Execute script

shell.evaluate(new File("src/geocode.groovy"));

 

 

 

 

using binding

assertEquals(51.475,

 

 

 

 

 

Double.parseDouble(

(String) binding.getVariable("lat")),0.001);

 

Retrieve

 

assertEquals(0.00143,

output

Double.parseDouble(

variables

(String) binding.getVariable("lng")),0.001);

 

 

 

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

}catch (IOException e) {

e.printStackTrace();

}

}

Passing parameters into the script is easy enough using the setVariable method on the Binding. The binding is then used as an argument to the GroovyShell constructor. The script is run from Java using the evaluate method as usual, and the results are extracted by getting the output variables from the shell. Using a GroovyShell and Binding is illustrated in figure 3.5.

There’s more to the GroovyShell than I’m presenting here. I can use the parse method, rather than evaluate, to parse the script and retrieve a reference to the generated Script object. That way I can set the binding variables and rerun the script without having to recompile every time. GroovyShell also works with a hierarchy of classloaders and configurations. Allthough all of that is interesting, it doesn’t really

www.it-ebooks.info

 

 

 

Executing Groovy scripts from Java

59

 

 

setVariable

 

 

 

 

 

 

 

 

 

 

Binding

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

(Groovy)

 

 

 

 

 

 

 

getVariable

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Java class

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Evaluate

 

 

 

 

 

 

 

GroovyShell

 

 

 

 

 

 

 

 

 

 

 

Script in Groovy

 

 

 

Evaluate

 

(Groovy)

 

Parse

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Figure 3.5 Java code sets variables in the Binding, which is used in the GroovyShell to execute Groovy code. The results are returned via the getVariable method in

the Binding.

add a lot to the integration story, so I’ll refer you to Dierk Koenig’s most excellent

Groovy in Action for details.

THE HARD WAY Use the ScriptEngine class from Java, or the Eval and GroovyShell classes from Groovy, along with a Binding if necessary, to call Groovy scripts from Java.

Between the ScriptEngine, Eval, and GroovyShell classes, hopefully you’ll agree that there are a variety of ways to execute Groovy scripts from Java. Collectively I still refer to this as “the hard way,” though it isn’t terribly hard, but it’s awfully indirect compared to the easy way. From now on I’ll stop trying to maintain the artificial separation of Java code from Groovy code. In order to make progress all I need to do is put the Groovy code into a class.

3.2.4Calling Groovy from Java the easy way

All the techniques I’ve discussed so far—using the JSR 223 ScriptEngine, or using the Groovy API classes Eval and GroovyShell—work just fine but feel overly complicated. Groovy is supposed to simplify your life, so although the mechanisms shown in the previous section all work, for most use cases there’s an easier way.

The easiest way to call Groovy from Java is to put the Groovy code in a class and compile it. Then Java code can instantiate the class and invoke its methods the normal way.

THE EASY WAY To call Groovy from Java, put the Groovy code in a class, compile it as usual, and then instantiate it and invoke methods as though it was Java.

Let’s return, once again, to the geocoder. This time, however, I’ll refactor it into a class that can be instantiated, with methods that can be invoked from outside. The process is shown in figure 3.6.

As the figure shows, the Java application will use a Location class to store all the needed attributes. It will supply the street, city, and state fields as input parameters, but the Location class will also include latitude and longitude fields that will be updated by the Groovy geocoder. The geocoder itself will be written in Groovy, because it’s easy to write the RESTful web service client code that way.7

7Note this is just like the geocoder with the Stadium class used in chapter 2 when I discussed the Groovy Baseball application. The differences here are the CSV output and that I’m invoking the Groovy implementation from Java.

www.it-ebooks.info

60

 

 

CHAPTER 3 Code-level integration

 

 

new, setStreet,

 

 

 

 

setCity, setState

 

class Location

 

 

 

 

 

(POJO or POGO)

 

 

 

 

 

 

Java application

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

class Geocoder

 

 

fillInLatLng(Location)

 

(Groovy)

 

 

 

 

updates

lat, lng

 

 

Figure 3.6 Mixing Java and Groovy classes. The Java app instantiates a Location and supplies it with street, city, and state values. It sends the new Location to the Groovy geocoder, whose fillInLatLng method supplies the latitude and longitude, which can then be retrieved by Java again.

Here’s the new Location class, which could be written in either Java or Groovy. This time, to keep the code simple I’ll use a Groovy POGO:

class Location { String street String city String state

double latitude double longitude

}

The Location class encapsulates the address information in strings and provides double variables for the latitude and longitude values that will be set using the geocoder. Speaking of the geocoder, the next listing shows a revised version that wraps the script into a class.

Listing 3.6 A Groovy class for geocoding

class Geocoder {

def base = 'http://maps.google.com/maps/geo?'

void fillInLatLong(Location loc) { def addressFields = loc.street ?

[loc.street,loc.city,loc.state] : [loc.city,loc.state] def address = addressFields.collect {

URLEncoder.encode(it,'UTF-8') }.join(',')

def params = [q:address,sensor:false, output:'csv',key:'ABQIAAAAa…']

def url = base + params.collect { k,v -> "$k=$v" }.join('&') def (code,level,lat,lng) = url.toURL().text.split(',') loc.latitude = lat.toDouble()

loc.longitude = lng.toDouble()

}

}

The fillInLatLong method takes a Location as an argument. Strictly speaking, I didn’t have to declare a type for the parameter at all. I could have relied on duck typing

www.it-ebooks.info

Executing Groovy scripts from Java

61

within the method and just been careful not to call it with anything other than an object with street, city, and state properties. Still, I’m building the service with a Location in mind, so it doesn’t hurt to say so.

The addressFields variable uses the ternary operator to determine whether or not a street has been supplied when returning the collection of address components. Note that I’m appealing to the so-called “Groovy truth” here, in that I don’t need to compare loc.street to null or an empty string explicitly. Any non-blank value of the street field as part of the loc argument will return true, so it will be added to the collection.

The rest of the class is the same as the previous script, though to make the class more useful I went to the trouble of converting the string results to doubles before returning the location.

One final issue is notable, and it highlights an important difference between a script and a class. All of the variables, whether they are local variables or attributes, have to be declared. There are no undefined variables, so there’s also no binding to worry about any more.

How do I use these classes (Geocoder and Location) from Java? Just instantiate them and call methods as usual. In the previous section I started accumulating JUnit 4 tests into a test class. Here’s another test to add to that set:

@Test

public void testGeocoder() { Location loc = new Location();

loc.setState("1600 Pennsylvania Avenue"); loc.setCity("Washington"); loc.setState("DC");

Geocoder geocoder = new Geocoder(); geocoder.fillInLatLong(loc); assertEquals(38.895,loc.getLatitude(),0.001); assertEquals(-77.037,loc.getLongitude(),0.001);

}

It doesn’t get much easier than that. I don’t need to instantiate a script engine or worry about Groovy shells or class loaders. Just instantiate and populate a Location, instantiate a Geocoder, and invoke the desired method.

From now on all of the examples I show will do integration the easy way. Again, this isn’t a value judgment against all the techniques demonstrated earlier in the chapter. If you want to call an existing Groovy script from Java, or you’re required to keep Java and Groovy code separate in your application, the previous mechanisms all work. Freely intermixing classes the way this script does, however, is very easy.

One last issue remains before I start looking at how Groovy might help Java. So far in this chapter the goal was always to call Groovy from Java. What about the other direction? How do you call Java from Groovy?

www.it-ebooks.info

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