Groovy: Difference between revisions
(→List) |
|||
(57 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
=External= | |||
* API: https://groovy-lang.org/gdk.html | |||
=TODO= | =TODO= | ||
* Tutorial: www.tutorialspoint.com/groovy/index.htm | |||
<Font color=red>TODO Groovy basics: https://docs.gradle.org/current/userguide/writing_build_scripts.html#groovy-dsl-basics</font> | <Font color=red>TODO Groovy basics: https://docs.gradle.org/current/userguide/writing_build_scripts.html#groovy-dsl-basics</font> | ||
= | =Overview= | ||
Introducing Groovy to a Java project is a simple as adding the Groovy JAR file to its classpath. | |||
To experiment with syntax, use the IntelliJ Groovy Console: | |||
{{Internal|IntelliJ_Groovy_Support#Groovy_Console|IntelliJ Groovy Console}} | |||
=<span id='Data_Structures'></span>Data Types= | |||
< | ==<span id='String_Programming'></span><span id='Strings'></span>String== | ||
==Single-Quoted vs. Double-Quoted Strings== | ===Single-Quoted vs. Double-Quoted Strings=== | ||
Groovy has both double-quoted and single-quoted String literals. The main difference is that double-quoted String literals support String interpolation: | Groovy has both double-quoted and single-quoted String literals. The main difference is that double-quoted String literals support String interpolation: | ||
Line 15: | Line 27: | ||
println "result is $x" // prints: result is 10 | println "result is $x" // prints: result is 10 | ||
</syntaxhighlight> | </syntaxhighlight> | ||
===Rules for String Interpolation=== | |||
Groovy supports declaring a string with either single quotes or double quotes. | |||
If the string is single-quoted, the variable interpolation behavior is turned off, similarly to how [[Bash_Parameter_and_Variable_Expansion#Overview|bash handles single-quoted strings]]. | |||
<syntaxhighlight lang='groovy'> | |||
def user="Elemental" | |||
echo 'Hello ${user}' | |||
</syntaxhighlight> | |||
will output: | |||
<syntaxhighlight lang='text'> | |||
Hello ${user} | |||
</syntaxhighlight> | |||
Double-quoted string support dollar-sign based interpolation: | |||
<syntaxhighlight lang='groovy'> | |||
def user="Elemental" | |||
echo "Hello ${user}" | |||
</syntaxhighlight> | |||
will output: | |||
<syntaxhighlight lang='text'> | |||
Hello Elemental | |||
</syntaxhighlight> | |||
Note that the variable passed to ${} can be a function that returns a String: | |||
<syntaxhighlight lang='groovy'> | |||
static indent(String lead, String object) { | |||
return lead + object | |||
} | |||
def a = "something" | |||
print "${indent("----", a)}" | |||
</syntaxhighlight> | |||
prints: | |||
<syntaxhighlight lang='text'> | |||
----something | |||
</syntaxhighlight> | |||
===Multi-Line Strings=== | |||
Multi-line strings can be represented in Groovy by enclosing them in <code>"""</code> or <code>'''</code>: | |||
<syntaxhighlight lang='groovy'> | |||
print """ | |||
this is | |||
a multi-line | |||
string | |||
""".stripIndent() | |||
</syntaxhighlight> | |||
<syntaxhighlight lang='groovy'> | |||
print ''' | |||
this is | |||
a multi-line | |||
string | |||
'''.stripIndent() | |||
</syntaxhighlight> | |||
The difference between <code>"""</code> or <code>'''</code> is in how variable interpolation is handled when variables are declared inside the quoted string. The rules describe here apply: [[#Rules_for_String_Interpolation|Rules for String Interpolation]]. As such, the first example below will interpolate the variable, while the second will not: | |||
<syntaxhighlight lang='groovy'> | |||
def v="multi-line" | |||
print """ | |||
this is | |||
a ${v} | |||
string | |||
""".stripIndent() | |||
</syntaxhighlight> | |||
will display: | |||
<syntaxhighlight lang='text'> | |||
this is | |||
a multi-line | |||
string | |||
</syntaxhighlight> | |||
while this | |||
<syntaxhighlight lang='groovy'> | |||
def v="multi-line" | |||
print ''' | |||
this is | |||
a ${v} | |||
string | |||
'''.stripIndent() | |||
</syntaxhighlight> | |||
will display: | |||
<syntaxhighlight lang='text'> | |||
this is | |||
a ${v} | |||
string | |||
</syntaxhighlight> | |||
===String Comparison=== | |||
Operators == and != work with strings. <font color=darkgray>Explain why.</font> | |||
==Array== | |||
<syntaxhighlight lang='groovy'> | |||
String[] arr = ["a", "b", "c"] | |||
def arr = ["a", "b", "c"].toArray() | |||
</syntaxhighlight> | |||
==Boolean== | |||
A boolean variable evaluates to true if assigned a different type variable that is not null or not empty, and false otherwise. | |||
The following expressions assign "true" to b: | |||
<syntaxhighlight lang='groovy'> | |||
boolean b | |||
b = "something" | |||
HashSet s = new HashSet() | |||
s.add("something") | |||
b = s | |||
</syntaxhighlight> | |||
The following expressions assign "false" to b: | |||
<syntaxhighlight lang='groovy'> | |||
boolean b | |||
b = "" | |||
b = new ArrayList() | |||
b = new HashSet() | |||
</syntaxhighlight> | |||
==List== | |||
<syntaxhighlight lang='groovy'> | |||
def list = ['A', 'B', 'CD'] | |||
</syntaxhighlight> | |||
Internally is stored as an <code>java.util.ArrayList</code>. | |||
To access an element: | |||
<syntaxhighlight lang='groovy'> | |||
list.get(0) | |||
list[0] | |||
</syntaxhighlight> | |||
To prepend an element: | |||
<syntaxhighlight lang='groovy'> | |||
list = ['X'] + list | |||
</syntaxhighlight> | |||
==Map== | |||
Empty map: | |||
<syntaxhighlight lang='groovy'> | |||
Map testingBranches = [:] | |||
</syntaxhighlight> | |||
Initialization: | |||
<syntaxhighlight lang='groovy'> | |||
def something = [a: 10, b:20, c:30] | |||
</syntaxhighlight> | |||
<syntaxhighlight lang='groovy'> | |||
def artifacts = [ | |||
"chart-A": [ | |||
"watchFor": ["src/charts/chart-A"], | |||
"chartName": "a", | |||
], | |||
"chart-B": [ | |||
"watchFor": ["src/charts/chart-B"], | |||
"chartName": "b", | |||
], | |||
"script": [ | |||
"watchFor": ["src/bin/run", "src/ansible", "script/bin/lib/a.shlib"], | |||
] | |||
] | |||
</syntaxhighlight> | |||
Element insertion: | |||
<syntaxhighlight lang='groovy'> | |||
something.put('a', 10) | |||
something['a'] = 10 | |||
something.a = 10 | |||
something.'a' = 10 | |||
</syntaxhighlight> | |||
Element access: | |||
<syntaxhighlight lang='groovy'> | |||
something.get('a') | |||
something['a'] | |||
something.a | |||
something.'a' | |||
</syntaxhighlight> | |||
⚠️ Note that when m.n syntax is used, "n" is interpreted as a literal, not a variable, so even if the variable is defined and initialized, it will not be used. | |||
<syntaxhighlight lang='groovy'> | |||
something.put('a', 1) | |||
String b = 'a' | |||
something.b // ⚠️ will attempt to retrieve something.'b', not something.'a' | |||
</syntaxhighlight> | |||
Adding elements from another map: | |||
<syntaxhighlight lang='groovy'> | |||
something << ['d':40] | |||
</syntaxhighlight> | |||
Iterating over a map: | |||
{{External|https://www.baeldung.com/groovy-map-iterating}} | |||
<syntaxhighlight lang='groovy'> | |||
myMap.each { | |||
print "${it.key}=${it.value}" | |||
} | |||
</syntaxhighlight> | |||
=Variables= | |||
Variables can be defined using either their type (<code>String</code>) or with the keywords <code>def</code>, which is equivalent with <code>var</code> | |||
<syntaxhighlight lang='groovy'> | |||
String a | |||
def b | |||
var c | |||
</syntaxhighlight> | |||
<code>def</code>/<code>var</code> act as a replacement for the type name when the actual type is not provided (either because you don't care about it or you're relying on type inference). You can think about them as an alias to <code>Object</code>. <code>def</code> is used when we don't want to restrict the type of an object, and we want to change what can be assigned to a variable any time during runtime. | |||
For scripts, undeclared variables are assumed to come from the Script binding. | |||
=Working with Closures= | =Working with Closures= | ||
Line 29: | Line 263: | ||
When a closure does not explicitly define a parameter using the '->' syntax, the closure '''always'' defines an implicit parameter named "it". | When a closure does not explicitly define a parameter using the '->' syntax, the closure '''always'' defines an implicit parameter named "it". | ||
==Passing Closures to Methods== | |||
If the closure is the last argument for a method, it can be passed outside the argument list. | |||
{{External|https://mrhaki.blogspot.com/2009/11/groovy-goodness-passing-closures-to.html}} | |||
==Passing a Method as Closure== | |||
A closure can be passed as an argument of a method. The canonical form, where the result of the closure can be returned as the result of the invoking method, is: | |||
<syntaxhighlight lang='groovy'> | |||
public Object withPreliminarySetup(Closure mainLogic) { | |||
preliminarySetup() | |||
if (mainLogic) return mainLogic.call() | |||
return null | |||
} | |||
</syntaxhighlight> | |||
An alternative form where the result of the closure is discarded (<code>run()</code> returns void) is: | |||
<syntaxhighlight lang='groovy'> | |||
public void withPreliminarySetup(Closure mainLogic) { | |||
preliminarySetup() | |||
if (mainLogic) mainLogic.run() | |||
} | |||
</syntaxhighlight> | |||
<font color=darkgray>Further research this form:</font> | |||
<syntaxhighlight lang='groovy'> | |||
public void withPreliminarySetup(Closure mainLogic) { | |||
preliminarySetup() | |||
mainLogic() | |||
} | |||
</syntaxhighlight> | |||
Invocation - the test setup is prior performed on the stack and then doTestLogic1() and doTestLogic2() methods are invoked, wrapped in a closure: | |||
<syntaxhighlight lang='groovy'> | |||
withTestSetup({ | |||
doTestLogic1() | |||
doTestLogic2() | |||
}) | |||
</syntaxhighlight> | |||
=Template Engines= | |||
{{Internal|Groovy Template Engines|Groovy Template Engines}} | |||
=Files= | |||
<syntaxhighlight lang='groovy'> | |||
filename = 'example.txt' | |||
File f = new File(filename) | |||
def lines = f.readLines() | |||
for (line in lines) { | |||
// ... | |||
} | |||
</syntaxhighlight> | |||
<syntaxhighlight lang='groovy'> | |||
filename = 'example.txt' | |||
File f = new File(filename) | |||
f.eachLine({ | |||
if (!it.startsWith("#") && !it.trim().isEmpty()) { | |||
... | |||
} | |||
}) | |||
</syntaxhighlight> | |||
=Dynamic Keywords= | |||
=@Field= | |||
{{External|https://docs.groovy-lang.org/latest/html/gapi/groovy/transform/Field.html}} | |||
Variable annotation used for changing the scope of a variable within a script from being within the run method of the script to being at the class level for the script. The annotated variable will become a private field of the script class. | |||
<font color=darkgray>In Jenkins, if Jenkinsfile variables are not declared @Field, they are not available in methods defined in Jenkinsfile.</font> | |||
=Script= | |||
Groovy is automatically wrapping every program in a class called Script. | |||
=Methods= | |||
Methods may be invoked without parentheses-enclosed arguments. The following are equivalent: | |||
<syntaxhighlight lang='groovy'> | |||
log('something') | |||
</syntaxhighlight> | |||
<syntaxhighlight lang='groovy'> | |||
log 'something' | |||
</syntaxhighlight> | |||
If the method has multiple arguments, the parentheses can be omitted but the arguments must be separated by comma: | |||
<syntaxhighlight lang='groovy'> | |||
log 'something', 'something else' | |||
</syntaxhighlight> | |||
==Method Parameters with Default Values== | |||
Default values can be assigned to a parameter in a method: | |||
<syntaxhighlight lang='groovy'> | |||
def say(msg = 'Hello', name = 'world') { | |||
"$msg $name!" | |||
} | |||
</syntaxhighlight> | |||
⚠️ Avoid using default method parameters with constructors, some compilers (for example, the Jenkins runtime Groovy compiler) will complain: | |||
<syntaxhighlight lang='text'> | |||
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed: | |||
file:/var/lib/jenkins/groovyStash/jenkins-jenkinsfile-refactoring-86/playground/ClusterTestingInfo.groovy: -1: cannot reference this inside of this ((java.lang.Object) pipeline, (java.lang.String -> java.lang.String) serverRoot, (java.lang.String -> java.lang.String[]) testRoots, (java.lang.String -> java.lang.String[]) org.kohsuke.groovy.sandbox.impl.Checker.checkedGetProperty(this, false, false, CLUSTER_TEST_ROOTS))(....) before supertype constructor has been called | |||
. At [-1:-1] @ line -1, column -1. | |||
</syntaxhighlight> | |||
⚠️ Declaring a parameter with a default value makes it optional, meaning that it can be omitted when invoking the function, so if there is already a method with the same name and with the same signature (while omitting the optional parameter), the compiler will detect compilation error ("Method with signature a() is already defined in the class Blue"): | |||
<syntaxhighlight lang='groovy'> | |||
class Blue { | |||
public void a() { | |||
// ... | |||
} | |||
// will not compile | |||
public void a(String arg = "something") { | |||
// ... | |||
} | |||
} | |||
</syntaxhighlight> | |||
=Control Flow= | |||
==if/else== | |||
A null condition evaluates to false: | |||
<syntaxhighlight lang='groovy'> | |||
def a = null | |||
if (a) { | |||
println "blue" | |||
} | |||
else { | |||
println "red" | |||
} | |||
</syntaxhighlight> | |||
will display "red". | |||
=Gradle Support= | |||
{{Internal|Gradle Groovy Plugin|Gradle Groovy Plugin}} | |||
=Testing= | |||
{{External|https://groovy-lang.org/testing.html}} |
Latest revision as of 20:05, 19 May 2021
External
TODO
- Tutorial: www.tutorialspoint.com/groovy/index.htm
TODO Groovy basics: https://docs.gradle.org/current/userguide/writing_build_scripts.html#groovy-dsl-basics
Overview
Introducing Groovy to a Java project is a simple as adding the Groovy JAR file to its classpath.
To experiment with syntax, use the IntelliJ Groovy Console:
Data Types
String
Single-Quoted vs. Double-Quoted Strings
Groovy has both double-quoted and single-quoted String literals. The main difference is that double-quoted String literals support String interpolation:
def x = 10
println "result is $x" // prints: result is 10
Rules for String Interpolation
Groovy supports declaring a string with either single quotes or double quotes.
If the string is single-quoted, the variable interpolation behavior is turned off, similarly to how bash handles single-quoted strings.
def user="Elemental"
echo 'Hello ${user}'
will output:
Hello ${user}
Double-quoted string support dollar-sign based interpolation:
def user="Elemental"
echo "Hello ${user}"
will output:
Hello Elemental
Note that the variable passed to ${} can be a function that returns a String:
static indent(String lead, String object) {
return lead + object
}
def a = "something"
print "${indent("----", a)}"
prints:
----something
Multi-Line Strings
Multi-line strings can be represented in Groovy by enclosing them in """
or '''
:
print """
this is
a multi-line
string
""".stripIndent()
print '''
this is
a multi-line
string
'''.stripIndent()
The difference between """
or '''
is in how variable interpolation is handled when variables are declared inside the quoted string. The rules describe here apply: Rules for String Interpolation. As such, the first example below will interpolate the variable, while the second will not:
def v="multi-line"
print """
this is
a ${v}
string
""".stripIndent()
will display:
this is
a multi-line
string
while this
def v="multi-line"
print '''
this is
a ${v}
string
'''.stripIndent()
will display:
this is
a ${v}
string
String Comparison
Operators == and != work with strings. Explain why.
Array
String[] arr = ["a", "b", "c"]
def arr = ["a", "b", "c"].toArray()
Boolean
A boolean variable evaluates to true if assigned a different type variable that is not null or not empty, and false otherwise.
The following expressions assign "true" to b:
boolean b
b = "something"
HashSet s = new HashSet()
s.add("something")
b = s
The following expressions assign "false" to b:
boolean b
b = ""
b = new ArrayList()
b = new HashSet()
List
def list = ['A', 'B', 'CD']
Internally is stored as an java.util.ArrayList
.
To access an element:
list.get(0)
list[0]
To prepend an element:
list = ['X'] + list
Map
Empty map:
Map testingBranches = [:]
Initialization:
def something = [a: 10, b:20, c:30]
def artifacts = [
"chart-A": [
"watchFor": ["src/charts/chart-A"],
"chartName": "a",
],
"chart-B": [
"watchFor": ["src/charts/chart-B"],
"chartName": "b",
],
"script": [
"watchFor": ["src/bin/run", "src/ansible", "script/bin/lib/a.shlib"],
]
]
Element insertion:
something.put('a', 10)
something['a'] = 10
something.a = 10
something.'a' = 10
Element access:
something.get('a')
something['a']
something.a
something.'a'
⚠️ Note that when m.n syntax is used, "n" is interpreted as a literal, not a variable, so even if the variable is defined and initialized, it will not be used.
something.put('a', 1)
String b = 'a'
something.b // ⚠️ will attempt to retrieve something.'b', not something.'a'
Adding elements from another map:
something << ['d':40]
Iterating over a map:
myMap.each {
print "${it.key}=${it.value}"
}
Variables
Variables can be defined using either their type (String
) or with the keywords def
, which is equivalent with var
String a
def b
var c
def
/var
act as a replacement for the type name when the actual type is not provided (either because you don't care about it or you're relying on type inference). You can think about them as an alias to Object
. def
is used when we don't want to restrict the type of an object, and we want to change what can be assigned to a variable any time during runtime.
For scripts, undeclared variables are assumed to come from the Script binding.
Working with Closures
Defining a Closure
def myClosure = { e -> println "Clicked on $e.source" }
Implicit Paramenter
When a closure does not explicitly define a parameter using the '->' syntax, the closure 'always defines an implicit parameter named "it".
Passing Closures to Methods
If the closure is the last argument for a method, it can be passed outside the argument list.
Passing a Method as Closure
A closure can be passed as an argument of a method. The canonical form, where the result of the closure can be returned as the result of the invoking method, is:
public Object withPreliminarySetup(Closure mainLogic) {
preliminarySetup()
if (mainLogic) return mainLogic.call()
return null
}
An alternative form where the result of the closure is discarded (run()
returns void) is:
public void withPreliminarySetup(Closure mainLogic) {
preliminarySetup()
if (mainLogic) mainLogic.run()
}
Further research this form:
public void withPreliminarySetup(Closure mainLogic) {
preliminarySetup()
mainLogic()
}
Invocation - the test setup is prior performed on the stack and then doTestLogic1() and doTestLogic2() methods are invoked, wrapped in a closure:
withTestSetup({
doTestLogic1()
doTestLogic2()
})
Template Engines
Files
filename = 'example.txt'
File f = new File(filename)
def lines = f.readLines()
for (line in lines) {
// ...
}
filename = 'example.txt'
File f = new File(filename)
f.eachLine({
if (!it.startsWith("#") && !it.trim().isEmpty()) {
...
}
})
Dynamic Keywords
@Field
Variable annotation used for changing the scope of a variable within a script from being within the run method of the script to being at the class level for the script. The annotated variable will become a private field of the script class.
In Jenkins, if Jenkinsfile variables are not declared @Field, they are not available in methods defined in Jenkinsfile.
Script
Groovy is automatically wrapping every program in a class called Script.
Methods
Methods may be invoked without parentheses-enclosed arguments. The following are equivalent:
log('something')
log 'something'
If the method has multiple arguments, the parentheses can be omitted but the arguments must be separated by comma:
log 'something', 'something else'
Method Parameters with Default Values
Default values can be assigned to a parameter in a method:
def say(msg = 'Hello', name = 'world') {
"$msg $name!"
}
⚠️ Avoid using default method parameters with constructors, some compilers (for example, the Jenkins runtime Groovy compiler) will complain:
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
file:/var/lib/jenkins/groovyStash/jenkins-jenkinsfile-refactoring-86/playground/ClusterTestingInfo.groovy: -1: cannot reference this inside of this ((java.lang.Object) pipeline, (java.lang.String -> java.lang.String) serverRoot, (java.lang.String -> java.lang.String[]) testRoots, (java.lang.String -> java.lang.String[]) org.kohsuke.groovy.sandbox.impl.Checker.checkedGetProperty(this, false, false, CLUSTER_TEST_ROOTS))(....) before supertype constructor has been called
. At [-1:-1] @ line -1, column -1.
⚠️ Declaring a parameter with a default value makes it optional, meaning that it can be omitted when invoking the function, so if there is already a method with the same name and with the same signature (while omitting the optional parameter), the compiler will detect compilation error ("Method with signature a() is already defined in the class Blue"):
class Blue {
public void a() {
// ...
}
// will not compile
public void a(String arg = "something") {
// ...
}
}
Control Flow
if/else
A null condition evaluates to false:
def a = null
if (a) {
println "blue"
}
else {
println "red"
}
will display "red".