Appendix A. Just Enough Groovy to Get By

This appendix reviews the basics of the Groovy programming language. The Gradle build files consist largely of a Domain Specific Language, written in Groovy, for builds. In addition to the DSL, any legal Groovy code can be added to the build.

Groovy is a general-purpose programming language, based on Java, that compiles to Java byte codes. While it has functional capabilities, it is an object-oriented language that is arguably the next-generation language in the path from C++ to Java.

Basic Syntax

The “Hello, World!” program for Groovy is the one-liner shown in Example A-1.

Example A-1. Hello, World! in Groovy
println 'Hello, World!'

Items of note:

  • Semicolons are optional. If you add them, they work, but they’re not required.

  • Parentheses are optional until they’re not. If the compiler guesses correctly where they should have gone, everything works. Otherwise, add them back in. The println method takes a String argument. Here the parentheses are left out.

  • There are two types of strings in Groovy: single-quoted strings, like Hello, are instances of java.lang.String. Double-quoted strings are Groovy strings and allow interpolation, shown in Example A-2.

There are no “primitives” in Groovy. All variables use the wrapper classes, like java.lang.Integer, java.lang.Character, and java.lang.Double. The native data type for integer literals, like 3, is Integer. The native data type for floating point literals, like 3.5, is java.math.BigDecimal.

Example A-2. Some basic data types in Groovy
assert 3.class == Integer
assert (3.5).class == BigDecimal
assert 'abc' instanceof String  1
assert "abc" instanceof String  2

String name = 'Dolly'
assert "Hello, ${name}!" == 'Hello, Dolly!' 3
assert "Hello, $name!" == 'Hello, Dolly!'   4
assert "Hello, $name!" instanceof GString
1

Single-quoted strings are Java strings

2

Double-quoted strings are also Java strings unless you interpolate

3

String interpolation, full form

4

String interpolation, short form when there is no ambiguity

Note that you can invoke methods on literals, because they are instances of the wrapper classes.

Groovy lets you declare variables with either an actual type, like String, Date, or Employee, or you can use def. See Example A-3.

Example A-3. Static versus dynamic data types
Integer n = 3
Date now = new Date()

def x = 3
assert x.class == Integer
x = 'abc'
assert x.class == String
x = new Date()
assert x.class == Date

Java imports the java.lang package automatically. In Groovy, the following packages are all automatically imported:

  • java.lang

  • java.util

  • java.io

  • java.net

  • groovy.lang

  • groovy.util

The classes java.math.BigInteger and java.math.BigDecimal are also available without an import statement.

The assert Method and the Groovy Truth

The assert method in Groovy evaluates its argument according to the “Groovy Truth.” That means:

  • Nonzero numbers (positive and negative) are true

  • Nonempty collections, including strings, are true

  • Nonnull references are true

  • Boolean true is true

The Groovy Truth is illustrated in Example A-4.

Example A-4. The Groovy Truth
assert 3;     assert -1;  assert !0
assert 'abc'; assert !''; assert !""

assert [3, 1, 4, 1, 5, 9]
assert ![]

Asserts that pass return nothing. Asserts that fail throw an exception, as in Example A-5, with lots of debugging information included.

Example A-5. Failing assertions
int x = 5; int y = 7
assert 12 == x + y  // passes

assert 12 == 3 * x + 4.5 * y / (2/x + y**3)  // fails

The result of the failing assertion is shown in Example A-6.

Example A-6. Failing assert output
Exception thrown

Assertion failed:

assert 12 == 3 * x + 4.5 * y / (2/x + y**3)
          |    | | |     | | |   || | ||
          false| 5 |     | 7 |   |5 | |343
               15  |     31.5|   0.4| 7
                   |         |      343.4
                   |         0.0917297612
                   15.0917297612

	at ConsoleScript11.run(ConsoleScript11:4)

Operator Overloading

In Groovy, every operator corresponds to a method call. For example, the + sign invokes the plus method on Number. This is used extensively in the Groovy libraries. Some examples are shown in Example A-7.

Example A-7. Operator overloading
assert 3 + 4 == 3.plus(4)
assert 3 * 4 == 3.multiply(4)

assert 2**6 == 64
assert 2**6 == 2.power(6)

assert 'abc' * 3 == 'abcabcabc' // String.multiply(Number)
try {
    3 * 'abc'
} catch (MissingMethodException e) {
    // no Number.multiply(String) method
}

String s = 'this is a string'
assert s + ' and more' == 'this is a string and more'
assert s - 'is' == 'th is a string'
assert s - 'is' - 'is' == 'th  a string'

Date now = new Date()
Date tomorrow = now + 1 // Date.plus(Integer)
assert tomorrow - 1 == now  // Date.minus(Integer)

Groovy has an exponentiation operator, **, as shown.

In Java, the == operator checks that two references are assigned to the same object. In Groovy, == invokes the equals method, so it checks for equivalence rather than equality. If you want to check references, use the is method.

Collections

Groovy has native syntax for collections. Use square brackets and separate values by commas to create an ArrayList. You can use the as operator to convert one collection type to another. Collections also have operator overloading, implementing methods like plus, minus, and multiply (Example A-8).

Example A-8. Collection examples and methods
def nums = [3, 1, 4, 1, 5, 9, 2, 6, 5]
assert nums instanceof ArrayList

Set uniques = nums as Set
assert uniques == [3, 1, 4, 5, 9, 2, 6] as Set

def sorted = nums as SortedSet
assert sorted == [1, 2, 3, 4, 5, 6, 9] as SortedSet
assert sorted instanceof TreeSet

assert nums[0] == 3
assert nums[1] == 1
assert nums[-1] == 5 // end of list
assert nums[-2] == 6

assert nums[0..3] == [3, 1, 4, 1]  // two dots is a Range
assert nums[-3..-1] == [2, 6, 5]
assert nums[-1..-3] == [5, 6, 2]

String hello = 'hello'
assert 'olleh' == hello[-1..0]  // Strings are collections too

A Range in Groovy consists of two values separated by a pair of dots, as in from..to. The range expands starting at the from position, invoking next on each element until it reaches the to position, inclusive.

Maps use a colon notation to separate the keys from the values. The square bracket operator on a map is the getAt or putAt method, depending on whether you are accessing or adding a value. The dot operator is overloaded similarly. See Example A-9 for details.

Example A-9. Map instances and methods
def map = [a:1, b:2, c:2]
assert map.getClass() == LinkedHashMap
assert map.a == 1  1
assert map['b'] == 2 2
assert map.get('c') == 2  3
1

Overloaded dot is put here

2

Uses putAt method

3

Java still works, too

Closures

Groovy has a class called Closure that represents a block of code that can be used like an object. Think of it as the body of an anonymous method, which is an oversimplification but not a bad start.

A closure is like a Java 8 lambda, in that it takes arguments and evaluates a block of code. Groovy closures can modify variables defined outside them, however, and Java 8 does not have a class called Lambda.

Many methods in Groovy take closures as arguments. For example, the each method on collections supplies each element to a closure, which is evaluated with it. An example is in Example A-10.

Example A-10. Using Groovy’s each method with a closure argument
def nums = [3, 1, 4, 1, 5, 9]

def doubles = []  1
nums.each { n ->  2
    doubles << n * 2  3
}

assert doubles == [6, 2, 8, 2, 10, 18]
1

Empty list

2

each takes a closure of one argument, before the arrow, here called n

3

Left-shift operator appends to a collection

Note

Modifying a variable defined outside a closure is considered a side-effect, and not good practice. The collect method, discussed later, is preferred.

This is a natural way to double the values in a list, but there is a better alternative, called collect. The collect method transforms a collection into a new one by applying a closure to each element. It is similar to the map method from Java 8, or just think of it as the map operation in a map-filter-reduce process (Example A-11).

Example A-11. Using Groovy’s collect method to transform a collection
def nums = [3, 1, 4, 1, 5, 9]
def doubles == nums.collect { it * 2 }
assert doubles == [6, 2, 8, 2, 10, 18]

When a closure has a single argument (which is the default), and you don’t give that argument a name using the arrow operator, the dummy name defaults to the word it. In this case, the collect method creates the doubles collection by applying it * 2 in a closure to each element.

POGOs

Java classes with just attributes and getters and setters are often called Plain Old Java Objects, or POJOs. Groovy has similar classes called POGOs. An example is in Example A-12.

Example A-12. A simple POGO
import groovy.transform.Canonical
@Canonical
class Event {
    String name
    Date when
    int priority
}

This little class actually has a lot of power. For a POGO:

  • The class is public by default

  • Attributes are private by default

  • Methods are public by default

  • Getter and setter methods are generated for each attribute not marked public or private

  • Both a default constructor and a “map-based” constructor (uses arguments of the form “attribute:value”) are provided

In addition, this POGO include the @Canonical annotation, which triggers an Abstract Syntax Tree (AST) transformation. AST transformations modify the syntax tree created by the compiler during the compilation process in specific ways.

The @Canonical annotation is actually a shortcut for three other AST transformations: @ToString, @EqualsAndHashCode, and @TupleConstructor. Each does what they sound like, so in this case, the @Canonical annotation adds to this class:

  • A toString override that displays the fully-qualified name of the class, followed by the values of the attributes, in order from top down

  • An equals override that does a null-safe check for equivalence on each attribute

  • A hashCode override that generates an integer based on the values of the attributes in a fashion similar to that laid out by Joshua Bloch in his Effective Java (Addison-Wesley) book long ago

  • An additional constructor that takes the attributes as arguments, in order

That’s a lot of productivity for seven lines of code. Example A-13 shows how to use it.

Example A-13. Using the Event POGO
Event e1 = new Event(name: 'Android Studio 1.0',
    when: Date.parse('MMM dd, yyyy', 'Dec 8, 2014'),
    priority: 1)

Event e2 = new Event(name: 'Android Studio 1.0',
    when: Date.parse('MMM dd, yyyy', 'Dec 8, 2014'),
    priority: 1)

assert e1.toString() ==
    'Event(Android Studio 1.0, Mon Dec 08 00:00:00 EST 2014, 1)'
assert e1 == e2

Set events = [e1, e2]
assert events.size() == 1

Gradle uses all these features, and more, but this summary should get you started.

Groovy in Gradle Build Files

Gradle build files support all Groovy syntax. Here are few specific examples, however, that illustrate Groovy in Gradle.

In Example A-14, the word apply is a method on the Project instance. The parentheses on the method are optional, and left out here. The argument is setting a property called plugin on the Project instance to the string value supplied.

Example A-14. Applying the Android plugin for Gradle
apply plugin: 'com.android.application'

In Example A-15, the term android is part of the plug-in’s DSL, which takes a closure as an argument. Properties inside the closure, like compileSdkVersion, are method calls with optional parentheses. In some Gradle build files, properties are assigned using =, which would invoke a corresponding setter method. The developers of the Android plug-in frequently added a regular method, like compileSdkVersion(23), in addition to the setter, setCompileSdkVersion(23).

Example A-15. Setting properties in the android block
android {
    compileSdkVersion 23
    buildToolsVersion "23.0.1"
}

Also, “nested” properties, like compileSdkVersion here, can be set using a dot notation as an alternative:

android.compileSdkVersion = 23

Both are equivalent.

Recent versions of the plug-in add a clean task to the Gradle build file. This task has name called clean, is an instance of the Delete class (as subclass of Task), and takes a closure as an argument. In keeping with standard Groovy practice, the closure is shown after the parentheses (Example A-16).

Example A-16. The default clean task
task clean(type: Delete) {
    delete rootProject.buildDir
}
Tip

If a Groovy method takes a Closure as its last argument, the closure is normally added after the parentheses.

The implementation here invokes the delete method (again, with optional parentheses) on the rootProject.buildDir. The value of the rootProject property is the top-level project, and the default value of buildDir is “build,” so this task deletes the “build” directory in the top-level project.

Note that calling clean in the top-level project will also invoke it on the app subproject, which will delete the build directory there as well.

In Example A-17, the compile term is part of the DSL, implying that its argument is applied during the compile phase. The fileTree method is shown with parentheses, though they could be left out. The dir argument takes a string representing a local directory. The include argument takes a Groovy list (the square brackets) of file patterns.

Example A-17. A file tree dependency
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
}

See Also

The book Making Java Groovy, by Ken Kousen (Manning), discusses Groovy and integrates it with Java, and also has a chapter on build processes with Gradle. The definitive reference for Groovy is Groovy in Action, Second Edition, by Dierk Konig, Paul King, et al. (Manning).

The Groovy home page is at http://groovy-lang.org, and contains extensive documentation.

O’Reilly also has three video courses on Groovy: Groovy Programming Fundamentals, Practical Groovy Programming, and Mastering Groovy Programming. All three are available on Safari as well.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.79.20