https://github.com/JetBrains/intellij-community
https://github.com/gradle/gradle
https://github.com/apache/groovy
And this is my current system states:
I myself recommend you to install both JDK version before the system module is released, 8 and the latest one, 10. This is because there are still many platforms, including Groovy that have not migrated to the module system since JDK 9. Or there are several libraries on JDK 8, while in JDK 9 until the latest one is no longer included in the default runtime images. This is due to modularity. So, the latest runtime images only includes some limited packages in the java.base module. You can still run Groovy properly using the latest JDK, as long as you have to manually add required modules that are no longer available into your custom runtime images.
Many Java developers have difficulty in developing software which relies on fast delivery of results, especially the Startup. The common issues are the syntax that is too static and its characteristic as statically typed programming language. So, in the case of productivity Java can't be superior against Python, Ruby or Node.js (JavaScript on server-side). Of course since the birth of JDK 7, the issues are out of date and things are getting better right now, because there are already many new and stable JVM-based programming languages that are awesome not only in increasing productivity, but also flexibility, mildness on the developer side in interpreting source codes and the most important point, the ecosystem becomes more solid. Related to this tutorial, I will present the coolest one, Groovy.
@groovy.transform.TypeCheked annotation, so that unexpected things and bugs can be prevented prematurely at compile time. Like a coin, if you see one side, you can't see the other side. So, when using this annotation for the sake of early prevention, then at the same time you will lose a lot of Groovy's powerful features. Not at all, please refer here for more details.Here are limited important idioms only to cover this tutorial. If you are experienced in Groovy, just skip this section.
return keyword is optional. So, any block of codes, such as method, function or closure will always return something (at least null) implicitly even you don't intend to return any value. Which one? The last statement that can produce a value.{ } which means a closure that returns null.call method.def closure1 = { }
def closure2 = { null }
closure1() //(1)
closure2.call() //(2)
assert closure1() == closure2.call() //The same result
it keyword. But this is no longer valid if the parameters are more than one.
def pow = { it * it }
//Equivalent to: pow = { it -> it * it }
assert pow(2) == 4
def multi(double a, double b = 1) { a * b } //The second parameter b has 1 as default value
def sum = { int a, int b = 0 -> a + b } //The second parameter of this closure has 0 as default value
assert multi(5) == sum(3, 2)
def append = { String prefix, Closure getText ->
prefix + getText().toString()
}
def result = append 'Lom', { 'bok' }
println result //It will print: Lombok
println append('Lom', { 'bok' }) //Closure of "append" has to invoke with parentheses, because it pass to "print" method as an argument.
$) and line separator for naming a block of codes by surrounding it using any quotes that form a literal string, but not interpolation.
def 'sum or concat two obj'(a, b) { a + b }
def append(String prefix, Closure getText) { prefix + getText() }
append('Lom') {
'bok'
}
//or
append 'Lom', {
'bok'
}
//or even without comma as argument separator
def append(Closure<String> getPrefix, Closure getSuffix) { getPrefix() + getSuffix().toString() }
append { 'Lom' }
{
'bok'
}
@PackageScope annotation.class Person {
String name
}
def murez = new Person()
murez.name = 'Murez Nasution'
//Equivalent to: murez.setName('Murez Nasution')
println murez.name
//Equivalent to: println(murez.getName())
If you have a some experiences, you will realize that Groovy does not support the do-while loop as Java can do. Groovy does not officially explain the reason, but we can easily analyze it as follows:
Suppose you have an interface,
interface Clause {
While(boolean condition)
}
and a function named Do that accept a closure as an argument,
Clause Do(Closure loop) {
new Clause() {
@Override
def "while"(boolean condition) {
condition
}
}
}
then you can easily invoke that function as,
Do {
. . .
} While(true)
The above statement will be ambiguous against a do-while loop. It becomes clear why it is not supported. So, I will raise this case to apply the custom syntax to replace a loop using Groovy DSL.
This project will be named as Lombok which provides DSL for iteration over the Java collections which presents a do-while loop. There are two ways to perform this initial creation,
Via IDE
com.murez as the GroupId and Lombok as the ArtifactId. About version, just ignore it.D:\Murez\Project\JVM\Groovy. So, this is my Project location,Alt + F12 or by accessing View ⇢ Tool Windows ⇢ Terminal, and executing the following command, gradle wrapper and then gradle build.Via Gradle Command
D:\Murez\Project\JVM\Groovy.gradle init --type groovy-library.D:\Murez\Project\JVM\Groovy\Lombok and click OK.The result will be the same as using the IDE method as before. It's just that by using the gradle command, init gradle [options], we have got the gradle wrapper at once and also the Groovy class sample in the src\main\groovy directory and also the sample test in the src\test\groovy.
Here are some basic patterns that will be applied to the customized loop syntax.
n > 0.
Loop.go {
//statements
} until positiveNumber
Loop.go {
//statements
} along { condition }
Loop.go {
//statements
} along collections
We will not be able to use the do and while words, because they have been used as the preserved keywords. Instead, we use go and until/along, because in my opinion they are simple and easy to remember.
If you are still confused, remember or refresh to section Groovy Quick Guide point 6 that parentheses are optional. Actually they are all equivalent as follows in Java code,
Closure statements = { /* statements */ };
Loop.go(statements).until(positiveNumber);
Loop.go(statements).along({ condition });
Loop.go(statements).along(collection);
Sometimes we need a state, which is an object that is always modified against every element in the collection along the loop. If the object has been modified in the first iteration, then it will be re-passed as an argument to the second iteration, and so on. Everything is OK without having to change our last syntax as follows,
int i = 0
Loop.go { //(1)
println "i = $i | it = $it"
++i
} until 5
//It will be the same as the following, but without declaring any integer variables
Loop.go { //(2)
if(it == null) //(3)
it = 0
println it
++it
} until 5
In case (2), the performance of loop will decrease because condition (3) will always be executed along the loop, whereas it is only needed in the first iteration. So, we provide a parameter at the go method which can accept an initial object that will be used as a state. We make the parameter have a default value to support backward compatibility. So that loop (2) will be as follows,
Loop.go(0) {
println it
++it
} until 5
So far our custom syntax is good, but I still have to demonstrate delegation of closure to you. So, we will try to cover the following issue,
def list = [ 'Murez', 'Nasution' ]
int n = list.size()
def builder = new StringBuilder('[')
for(int i = -1; ++i < n; ) {
builder.append('"').append(list.get(i)).append('"')
builder.append(',')
}
builder.append(']')
assert builder.toString() == '["Murez","Nasution",]'
println "${ builder.toString() } can't be parsed to JSON"
The resulting string of JSON array is still wrong, because there is a comma that is not expected in the last element. So, to solve this issue we will expand our syntax to be as follows,
Loop.go {
next { } //(1)
{
//the main statements
//can access _key and _val. (2)
}
} until positiveNumber
def list = [ 'Murez', 'Nasution' ]
Loop.go(new StringBuilder('[')) { //(3)
next { it.append(',') }
{
int index = _key()
it.append('"').append(list(index)).append('"')
return it
}
} along list
The following is the explanation.
next is a callback method to set a closure which will be called if there's a next element relative to this current cursor, and another one as main closure containing the main statements. It also can be invoked like this,
next({ }, { /*main statements*/ })
//or
next { }, {
/*main statements*/
}
//or
next({ }) {
/*main statements*/
}
//and finally
next { } {
/*main statements*/
}
In the second form like this, we can no longer supply the main statements in the scope of the outer closure. Instead, we must move them to the scope of the inner one which is the second argument on the next method.
//Instead of as follows
Loop.go(new StringBuilder('[')) {
next { it.append(',') }
{
//nothing to do
}
int index = _key()
it.append('"').append(list(index)).append('"')
it
} along list
//You should define it as follows
Loop.go(new StringBuilder('[')) {
next { it.append(',') }
{
int index = _key()
it.append('"').append(list(index)).append('"')
it
}
} along list
Why can this happen? Actually this is a form of contract. The user defines the actions against the loop by supplying it as an argument to the next method. However, since the user has declared his contract, it has not been accepted as long as the provider has not executed the outer closure.
boolean invoked = false
def setter = { action ->
println 'I am setter and has already invoked'
action()
invoked = true
}
def outer = {
setter { println 'I am a contract' }
}
println invoked //Still false, which means that the contract is still not accepted
outer() //By invoke the outer closure, setter has been called and a contract has already accepted
println invoked //Finally, true
Maybe you will be confused with this, but I'm sure you will understand after observing the implementation. The important thing I want to convey is the first form and the second one is the outer closure execution that has different result and interpretation._key field and elements in the collection by accessing the _val field.On the Project tab, right click on groovy directory and choose: New ⇢ Groovy Class. Then a new window will appear.
Enter com.murez.util.Loop and hit OK.
And the following is the source codes of Loop class,
package com.murez.util
import static groovy.lang.Closure.DELEGATE_FIRST as X
/**
* @author Murez Nasution
*/
class Loop {
static final STOP = new Object()
static go(def init = null, Closure loop) { new Loop(init, loop) }
private Closure loop, next
private v, k = 0
private init
private test = 'No! I am private.'
private Loop(def init, Closure loop) {
this.init = init
this.loop = loop
}
def until(int last) {
if(init == STOP) return STOP
def o
try { o = 'do'() }
catch(NullPointerException | NoSuchLoopException ignored) { return }
catch(e) { throw e }
if(k < last) {
if(next) {
for(; ++k < last; ) o = loop next(o)
} else
for(; ++k < last; ) o = loop o
}
}
def along(Closure<Boolean> condition) {
if(init == STOP) return STOP
def o
try { o = 'do'() }
catch(NullPointerException | NoSuchLoopException ignored) { return }
catch(e) { throw e }
if(condition) {
if(!next) {
for(; condition(++k); ) o = loop o
} else
for(; condition(++k); ) o = loop next(o)
}
}
def along(Iterable collection) {
if(init == STOP) return STOP
def i, o
try {
i = collection.iterator()
if(!(o = i.hasNext())) return o
v = i.next()
o = 'do'()
}
catch(NullPointerException | NoSuchLoopException ignored) { return }
catch(e) { throw e }
if(next) {
for(; i.hasNext(); o = loop o) {
o = next o
++k
v = i.next()
}
} else
for(; i.hasNext(); o = loop o) {
++k
v = i.next()
}
o
}
def along(Map entryPairs, Closure<Boolean> ctrl = null) {
if(init == STOP) return STOP
def o, i
try {
i = entryPairs.entrySet().iterator()
if(!(o = i.hasNext())) return o
set i
o = 'do'() }
catch(NullPointerException | NoSuchLoopException ignored) { return }
catch(e) { throw e }
if(next) {
for(; i.hasNext(); o = loop o) {
o = next o
set i
}
} else
for(; i.hasNext(); o = loop o) set i
o
}
private void set(Iterator<Map.Entry> i) {
def e = i.next()
k = e.key
v = e.value
}
private 'do'() {
try { loop.resolveStrategy = X }
catch(e) { throw e }
def args = [ flag: true ]
loop.delegate = new Delegator({ k }, { v }, {
nextAction, mainAction ->
next = nextAction
args.main = mainAction
args.flag = null
})
def o = loop init
if(!args.flag) {
if(!args.main) throw new NoSuchLoopException()
loop = args.main as Closure
loop init
} else o
}
private final class Delegator {
Closure<Void> next
Closure _key, _val
private Delegator(key, val, setter) {
_key = key
_val = val
next = setter
}
}
private final class NoSuchLoopException extends RuntimeException {
private NoSuchLoopException() {
super('No action to be performed')
}
}
}
As seen, the next method has never been defined, but user can use it without any error at runtime. This is because we have delegated an object created from the Delegator class to the target closure, which is main closure (in this class I named it as a loop). When a program does not find any variables or methods that have never been defined, it will try to look for it first to the delegated object. If the object never exists, then an error will occur.
We can also specify the delegation strategy by giving a delegation constant via the resolveStrategy property. Here I use DELEGATE_FIRST which means that if there are two definitions that are the same wherever the program can find it, the highest priority is the delegated object.
It's like creating a Groovy class before, but instead of choosing a class you should choose Groovy Script and be named as com.murez.Main. With Groovy script, you can write any codes directly without having to define the class first, like JavaScript.
Since we use the Gradle build tool, there are two ways to run the main program, i.e. via the IDE or Gradle. Well, running via an IDE is normal, just focus on the script or class that has the public static main(String[]) method and press the Ctrl + Shift + F10. But if via gradle, we have to change the build.gradle file a bit by adding the application plugin and setting the mainClassName property to the class name that will be executed as the main program, as follows,
plugins {
id 'groovy'
id 'application'
}
mainClassName = 'com.murez.Main'
sourceCompatibility = 1.8
targetCompatibility = 1.8
version = '0.0.1-SNAPSHOT'
group = 'com.murez'
repositories {
jcenter()
}
dependencies {
compile 'org.codehaus.groovy:groovy-all:2.4.15'
testCompile 'org.spockframework:spock-core:1.0-groovy-2.4'
}
Finally, just execute gradle run if you have installed Gradle on the local machine and with the same version or if not, you should use Gradle Wrapper gradlew run command (this is the recommended way) on the Terminal and gradle will show the output.
Now my project can be run easily by anyone just by executing gradlew run command, even if they don't have Gradle installed on the local machine. Everything is solved over the network and make sure you have internet access.
Finally, the following is a summary of the test
Loop.go {
println 'Just print once'
} along { false }
println '___________'
Loop.go {
println _key()
} until 5
println '___________'
int i = 0
Loop.go {
println "i = $i | index = ${ _key() }"
++i
} along { i < 5 }
def list = [ 'Murez', 'Nasution', 'Apache Groovy' ]
Loop.go {
println "index = ${ _key() } | value = ${ _val() }"
} along list
println '_____________________________________'
println()
def map = [ name: 'Murez Nasution', email: 'murez.nasution@gmail', contact: 963852741 ]
Loop.go {
println "key = ${ _key() } | value = ${ _val() }"
} along map
def person = [ name: 'Murez Nasution', email: 'murez.nasution@gmail', contact: 963852741 ]
def json = Loop.go person.size() > 0? new StringBuilder('{') : Loop.STOP, {
next { it.append ',' }
{
it.append '"' append _key() append '"' append ':'
def value
if((value = _val()) == null || value instanceof Number)
it.append value
else
it.append '"' append value append '"'
}
} along person append '}'
print json
https://github.com/murez-nst/JVM-Based/tree/master/Groovy/Lombok