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

114

CHAPTER 5 Build processes

5.5Grapes and @Grab

The Grape mechanism allows you to declare library dependencies directly inside a Groovy script. This is useful when you need to deliver a script to a client that doesn’t already have the required dependencies but is willing to download them as part of the build process.

The overall API is called Grape (Groovy Adaptable/Advanced Packaging Engine) and starts with the groovy.lang.Grab annotation. It uses an Ivy resolver to identify and download dependencies. Its primary use case is on scripts, so that they can be delivered to a client without any setup requirements other than having Groovy installed. At runtime Groovy will download and install any declared libraries and their transitive dependencies as part of the execution process.

GRAPE USE CASE Grape allows you to deliver a simple script that can be executed by a client without any setup necessary other than installing Groovy, making it particularly convenient for testers or QA people.

To demonstrate the Grape system, let me choose the Math library from the Apache Commons project (http://commons.apache.org/math/). Specifically, I want to work with the complex numbers package. The package includes a class called Complex, which represents complex numbers. Although the class is interesting in itself, it also makes for a nice demonstration of Groovy’s metaprogramming capabilities.

In Maven syntax the library has a group ID of org.apache.commons, an artifact ID of commons-math3, and a version of 3.0. Therefore, the format of the @Grab annotation is as shown in the following script:

import org.apache.commons.math3.complex.*

@Grab('org.apache.commons:commons-math3:3.0') Complex first = new Complex(1.0, 3.0); Complex second = new Complex(2.0, 5.0);

The @Grab annotation downloads both the given library and its dependencies. The syntax uses Maven structure, using colons to connect the group ID, the artifact ID, and the version number. Alternatively, you can specify the sections individually:

@Grab(group='org.apache.commons', module='commons-math3', version='3.0')

The behavior is equivalent in either case.

There isn’t much more to Grapes than this. In order to show an interesting example that requires an external Java library, let me present a simple case of Groovy metaprogramming. There’s nothing about it that requires Grapes in particular, but it shows how a small amount of metaprogramming can make a Java library class groovier. Using Grapes in the script allows me to send it to a client without compiling it or providing the library dependencies. The Grape annotations will handle the rest.

www.it-ebooks.info

Grapes and @Grab

115

The Complex class represents a complex number, which combines real and imaginary parts. The class contains a two-argument constructor, as shown, that takes the real and imaginary parts as parameters. Many methods are defined on the class, so that it generalizes basic numerical computations to the complex domain.

Recall that in Groovy every operator delegates to a method call. Interestingly enough, the Complex class already has a method called multiply for computing the product of two complex numbers. Because the * operator in Groovy uses the multiply method, that operator can be used immediately:

assert first.multiply(second) == first * second

Again, this is a Java class. Fortunately, the developers of the class chose to include a method called multiply, so Groovy can use the * operator with complex numbers.

What about all the other mathematical operations? Most don’t line up as cleanly. For example, the class uses add instead of plus and subtract instead of minus. It’s easy to connect them, however, by adding the appropriate methods to the metaclass associated with Complex when viewed through Groovy.

As a reminder, every class accessed through Groovy contains a metaclass, and the metaclass is an Expando. This means that methods and properties can be added to the metaclass as desired, and the resulting members will be part of any instantiated object. Here’s how to add several mathematical operations to Complex:

Complex.metaClass.plus = { Complex c -> delegate.add c } Complex.metaClass.minus = { Complex c -> delegate.subtract c } Complex.metaClass.div = { Complex c -> delegate.divide c } Complex.metaClass.power = { Complex c -> delegate.pow c } Complex.metaClass.negative = { delegate.negate() }

That takes care of the +, -, /, **, and negation operators, respectively. In each case, the relevant method is defined on the metaclass by setting it equal to a closure. The associated closure takes a Complex argument (in the case of binary operators) and invokes the desired existing method on the closure’s delegate, passing along the argument.

CLOSURE DELEGATES Every closure has a delegate property. By default the delegate points to the object that the closure was invoked on.

After adding those methods to the metaclass, the operators can be used in the Groovy script:

assert new Complex(3.0, 8.0) == first + second assert new Complex(1.0, 2.0) == second - first

assert new Complex(0.5862068965517241, 0.03448275862068969) == first / second

assert new Complex(-0.007563724861696302, 0.01786136835085382) == first ** second

assert new Complex(-1.0, -3.0) == -first

www.it-ebooks.info

116

CHAPTER 5 Build processes

To complete this part of the story I want to demonstrate the famous equation known as Euler’s identity,9 which is expressed as

e iπ = 1

This equation connects the imaginary numbers (i) and the transcendental numbers (e and π) to the negative numbers (–1). Euler found this expression so profound he had it inscribed on his tombstone.

The java.lang.Math class contains constants Math.E and Math.PI, and the Complex class has the constant Complex.I. To make the formula look better I’ll use static imports for all of them.

One final addition is necessary to make this work. Math.E in Java is of type double, and I want to raise it to a Complex power. The easiest way to do that is to convert the double to an instance of the Complex class and then use the pow method in the Complex class. Returning to Groovy metaprogramming, I need a power method (which corresponds to the ** operator) on Double that takes a Complex argument:

Double.metaClass.power = { Complex c -> (new Complex(delegate,0)).pow(c) }

With all that machinery in place the resulting code is a bit anticlimactic, but that’s a good thing:

Complex result = E ** (I * PI) assert result.real == -1

assert result.imaginary < 1.0e-15

As usual in Groovy, accessing the real or imaginary property is equivalent to calling the getReal or getImaginary method, respectively. The expression does generate a real part of –1, but the imaginary part isn’t exactly zero due to the round-off error associated with Java doubles. On my machine it evaluates to a number less than the bound shown, which is certainly close enough.

There are a few additional annotations available in the Grapes system. One is @GrabConfig, used in the next example when loading a database driver. The following script uses the groovy.sql.Sql class to generate an H2 database and add some data to it:

import groovy.sql.Sql

@GrabConfig(systemClassLoader=true) @Grab(group='com.h2database', module='h2', version='1.2.140')

Sql sql = Sql.newInstance(url:'jdbc:h2:mem:',driver:'org.h2.Driver')

The annotations provide the driver, so the Sql class can be used normally.

Because a member of a class can only have a single instance of a particular annotation, the @Grapes annotation is used to combine multiple @Grab annotations. The next listing computes complex values and stores them in a database table.

9Leonhard Euler (1707 – 1783) was one of the most brilliant mathematicians of all time. His work spanned virtually every field of math and science, and his collected works filled between 60 and 80 quarto volumes. The transcendental number e is named after him.

www.it-ebooks.info

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