tag:blogger.com,1999:blog-66710193984341414692024-03-14T09:45:30.532+01:00Messages from mrhakiA blog about Groovy, Clojure, Java, Gradle, Micronaut, Asciidoctor and other cool developer subjects.Hubert Klein Ikkinkhttp://www.blogger.com/profile/04670461522955162252noreply@blogger.comBlogger1466125tag:blogger.com,1999:blog-6671019398434141469.post-9306254518310430872024-03-08T15:48:00.003+01:002024-03-08T16:27:20.536+01:00Gradle Goodness: Organizing Tasks Using The Task Container<p>A Gradle build file describes what is needed to build our Java project.
We apply one or more plugins, configure the plugins, declare dependencies and create and configure tasks.
We have a lot of freedom to organize the build file as Gradle doesn’t really care.
So to create maintainable Gradle build files we need to organize our build files and follow some conventions.
In this post we focus on organizing the tasks and see if we can find a good way to do this.</p>
<p>It is good to have a single place where all the tasks are created and configured, instead of having all the logic scattered all over the build file.
The <code>TaskContainer</code> is a good place to put all the tasks.
To access the <code>TaskContainer</code> we can use the <code>tasks</code> property on the <code>Project</code> object.
Within the scope of the <code>tasks</code> block we can create and configure tasks.
Now we have a single place where all the tasks are created and configured.
This makes it easier to find the tasks in our project as we have a single place to look for the tasks.</p>
<p>Within the scope of the <code>TaskContainer</code> we can use a convention to put the task creation methods at the top of the <code>TaskContainer</code> block.
And the task configuration methods are after the task creation in the <code>TaskContainer</code> block.
The tasks that are created at the top of the <code>TaskContainer</code> scope can be referenced by configuration code for tasks later in the <code>TaskContainer</code> scope.</p>
<p>The following diagram shows the build file structure and an example of the implementation:</p>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh2RfHW-VHX-uvIeL5EiZyr-5aLuHrtsKZHfiVQNrcjuL8Ah6urWBl2JerOMt_lDEGwg3puDshvIRUow-rq57sP3IvLdGVsYVF3ZTUIksrYavGznnfa8W2433znjE1csCHospZ67yUtPu1d-TQq_jcRZ2fIXNa4QJ0YwlYlWB758FKWxq7fyUY0pBhoelWq/s1600/gradle-tasks.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="641" data-original-width="618" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh2RfHW-VHX-uvIeL5EiZyr-5aLuHrtsKZHfiVQNrcjuL8Ah6urWBl2JerOMt_lDEGwg3puDshvIRUow-rq57sP3IvLdGVsYVF3ZTUIksrYavGznnfa8W2433znjE1csCHospZ67yUtPu1d-TQq_jcRZ2fIXNa4QJ0YwlYlWB758FKWxq7fyUY0pBhoelWq/s1600/gradle-tasks.png"/></a></div>
<p>In the example Gradle build file for a Java project we organize the tasks in the <code>TaskContainer</code> using this convention:</p>
<pre class="brush:kotlin;">plugins {
java
}
...
tasks {
// ----------------------------------------------
// Task creation at the top of the container.
// ----------------------------------------------
// Register new task "uberJar".
val uberJar by registering(Jar::class) {
archiveClassifier = "uber"
from(sourceSets.main.get().output)
dependsOn(configurations.runtimeClasspath)
from({
configurations.runtimeClasspath.get()
.filter { it.name.endsWith("jar") }
.map { zipTree(it) }
})
}
// ----------------------------------------------
// Task configuration after task creation.
// ----------------------------------------------
// The output of the "uberJar" tasks is part of
// the output of the "assemble" task.
// We can refer to the "assemble" task directly
// as it is added by the Java plugin.
assemble {
// We can refer to the task name that
// we just created in our
// tasks configuration block.
dependsOn(uberJar)
}
// Configure tasks with type JavaCompile.
withType<JavaCompile>().configureEach {
options.compilerArgs.add("--enable-preview")
}
}
...</pre>
<p>Although Gradle doesn’t enforce us to use this convention it can be very helpful as build file authors to use it as it makes it easier to find the tasks in the project.</p>
<p>Written with Gradle 8.6.</p>Hubert Klein Ikkinkhttp://www.blogger.com/profile/04670461522955162252noreply@blogger.comtag:blogger.com,1999:blog-6671019398434141469.post-77574643370314410652024-03-06T22:18:00.001+01:002024-03-06T22:21:04.890+01:00IntelliJ HTTP Client: Parsing JSON Web Tokens<p>The IntelliJ HTTP Client is very useful for testing APIs.
We can use Javascript to look at the response and write tests with assertions about the response.
If an API returns a JSON Web Token (JWT), we can use a Javascript function to decode the token and extract information from it.
For example we can then assert that fields of the token have the correct value.
There is no built-in support in IntelliJ HTTP Client to decode a JWT, but we can write our own Javascript function to do it.
We then use the function in our Javascript response handler to decode the token.</p>
<p>In the following HTTP request file we simulate a call to get an response with a field containing an JWT.
We use the function <code>decodeJwt</code> from the file <code>jwt-utils.js</code> to decode the token and extract information from it.</p>
<pre class="brush:plain;">### Simulate a call to get an response with a field containing an JWT.
POST https://examples.http-client.intellij.net/anything
Content-Type: application/json
// Original token before it is base64 encoded:
// {
// "sub": "1234567890",
// "upn": "hubert@mrhaki.com",
// "name": "mrhaki",
// "groups": ["Blogger"],
// "iat": 1516239022
// }
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwidXBuIjoiaHViZXJ0QG1yaGFraS5jb20iLCJuYW1lIjoibXJoYWtpIiwiZ3JvdXBzIjpbIkJsb2dnZXIiXSwiaWF0IjoxNTE2MjM5MDIyfQ.9E2gYNFogs3K8pJH9JiJYISv403EtCm4tRzQWZi1CXM"
}
> {%
import {decodeJwt} from './scripts/jwt-utils';
// The token is in the response body and we get it
// using the path `json.token`.
// We store it as variable `token` so we can use in the next step.
const token = decodeJwt(response.body.json.token);
// We can write assertions on the token contents.
client.test("Check fields in token", function () {
client.assert(token.upn === "hubert@mrhaki.com");
client.assert(token.name === "mrhaki");
client.assert(token.groups.includes("Blogger"));
client.assert(token.sub === "1234567890");
client.assert(token.iat === 1516239022);
});
%}</pre>
<p>The function <code>decodeJwt</code> is defined in the file <code>jwt-utils.js</code>:</p>
<pre class="brush:javascript;">// File: ./scripts/jwt-utils.js
export function decodeJwt(token) {
var base64EncodedPayload = token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');
var jsonPayload = decodeURIComponent(decodeBase64(base64EncodedPayload)
.split('')
.map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
.join(''))
// Remove any NUL characters at the end of the string.
.replace(/\0+$/g, '');
return JSON.parse(jsonPayload);
}
function decodeBase64(input) {
const _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
let output = "";
let chr1, chr2, chr3;
let enc1, enc2, enc3, enc4;
let i = 0;
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
while (i < input.length) {
enc1 = _keyStr.indexOf(input.charAt(i++));
enc2 = _keyStr.indexOf(input.charAt(i++));
enc3 = _keyStr.indexOf(input.charAt(i++));
enc4 = _keyStr.indexOf(input.charAt(i++));
chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4;
output = output + String.fromCharCode(chr1);
if (enc3 !== 64) {
output = output + String.fromCharCode(chr2);
}
if (enc4 !== 64) {
output = output + String.fromCharCode(chr3);
}
}
return decodeURI(output);
}</pre>
<p>Written with IntelliJ IDEA 2023.3.4.</p>Hubert Klein Ikkinkhttp://www.blogger.com/profile/04670461522955162252noreply@blogger.comtag:blogger.com,1999:blog-6671019398434141469.post-57800835535458366132024-02-25T17:19:00.000+01:002024-02-25T17:19:08.903+01:00Gradle Goodness: Using System Properties Lazily<p>It is good practice in Gradle to use lazy configuration.
This makes builds faster as only configuration values are evaluated when needed.
We should try to not let Gradle spend time on evaluating configuration values that will not be used.
For example tasks that are not executed could still be configured by Gradle.
If we make sure the configuration of these tasks is lazy we can save time.</p>
<p>Gradle gives us a lazy way to get the value of a Java system property.
In our build script we can use the <code>providers</code> property of type <code>ProviderFactory</code> and the method <code>systemProperty(String)</code>.
This method returns a <code>Provider<String></code> instance that can be used to get the value of a system property in a lazy way.
The method <code>systemProperty</code> can also be used with a <code>Provider<String></code> argument.</p>
<p>In the following example we register two tasks that print the value of the Java system property <code>user.name</code> to the console.
We use lazy configuration to make sure the value of the system property is only fetched when the task is executed.</p>
<pre class="brush:kotlin;">tasks {
register<PrintSystemProperty>("printSystemProperty") {
// We can use providers.systemProperty(String)
// to get the value of an Java system property
// in a lazy way.
// The argument can also be a Provider<String> type.
// So at this point the value is not fetched yet,
// only when the task is executed the actual value
// of the system property "user.name" is fetched.
systemProperty = providers.systemProperty("user.name")
}
register<PrintSystemProperty>("printSystemProperty2") {
// Alternative way is to use the generic provider(Callable)
// method to get the value of a system property.
systemProperty = provider { System.getProperty("user.name") }
}
}
// Simple task to print the value of a Java system property.
abstract class PrintSystemProperty : DefaultTask() {
@get:Input
abstract val systemProperty: Property<String> // Use lazy property.
@TaskAction
fun printSystemPropertyValue() {
// Only here we actually will get the value
// for the system property.
logger.quiet(systemProperty.get())
}
}</pre>
<pre class="brush:plain;">$ ./gradlew printSystemProperty printSystemProperty2
> Task :printSystemProperty
mrhaki
> Task :printSystemProperty2
mrhaki
BUILD SUCCESSFUL in 685ms
2 actionable tasks: 2 executed</pre>
<p>Written with Gradle 8.6.</p>Hubert Klein Ikkinkhttp://www.blogger.com/profile/04670461522955162252noreply@blogger.comtag:blogger.com,1999:blog-6671019398434141469.post-5402047688705950962024-02-25T13:22:00.009+01:002024-02-25T17:09:50.579+01:00Gradle Goodness: Using Environment Variables Lazily<p>It is good practice in Gradle to use lazy configuration.
This makes builds faster as only configuration values are evaluated when needed.
We should try to not let Gradle spend time on evaluating configuration values that will not be used.
For example tasks that are not executed could still be configured by Gradle.
If we make sure the configuration of these tasks is lazy we can save time.</p>
<p>Gradle gives us a lazy way to get the value of an environment variable.
In our build script we can use the <code>providers</code> property of type <code>ProviderFactory</code> and the method <code>environmentVariable(String)</code>.
This method returns a <code>Provider<String></code> instance that can be used to get the value of an environment variable in a lazy way.</p>
<p>In the following example we register two tasks that print the value of the environment variable <code>USER</code>.
We use lazy configuration to make sure the value of the environment variable is only fetched when the task is executed.</p>
<pre class="brush:kotlin;">tasks {
register<PrintEnvironmentVariable>("printEnvironmentVariable") {
// We can use providers.environmentVariable(String)
// to get the value of an environment variable
// in a lazy way.
// The argument can also be a Provider<String> type.
// So at this point the value is not fetched yet,
// only when the task is executed the actual value
// of the environment variable "USER" is fetched.
environmentVariable = providers.environmentVariable("USER")
}
register<PrintEnvironmentVariable>("printEnvironmentVariable2") {
// Alternative way is to use the generic provider(Callable)
// method to get the value of an environment variable.
environmentVariable = provider { System.getenv("USER") }
}
}
// Simple task to print the value of an environment variable.
abstract class PrintEnvironmentVariable : DefaultTask() {
@get:Input
abstract val environmentVariable: Property<String> // Use lazy property.
@TaskAction
fun printEnvironmentVariable() {
// Only here we actually will get the value
// for the environment variable.
logger.quiet(environmentVariable.get())
}
}</pre>
<p>When we execute the tasks we see the value of the environment variable <code>USER</code>:</p>
<pre class="brush:plain;">
$ ./gradlew printEnvironmentVariable printEnvironmentVariable2
> Task :printEnvironmentVariable
mrhaki
> Task :printEnvironmentVariable2
mrhaki
BUILD SUCCESSFUL in 599ms
2 actionable tasks: 2 executed
</pre>
<p>Written with Gradle 8.6.</p>Hubert Klein Ikkinkhttp://www.blogger.com/profile/04670461522955162252noreply@blogger.comtag:blogger.com,1999:blog-6671019398434141469.post-54146376214816705062024-02-14T08:23:00.006+01:002024-02-19T07:09:37.941+01:00IntelliJ HTTP Client: Accessing Environment Variables In JavaScript<p>When we use the IntelliJ HTTP Client we can write JavaScript for the pre-request and response handlers.
If we want to access an environment variable in JavaScript we can use <code>request.environment.get(string)</code>.
The argument for the <code>get</code> function is the name of the environment variable we want to get the value for.
Environment variables can be defined in the file <code>http-client.env.json</code> or in <code>http-client.private.env.json</code>.</p>
<p>In the following example we have environment file <code>http-client.env.json</code> with two environments <code>development</code> and <code>production</code>.
Each environment has a variable named <code>project</code>:</p>
<pre class="brush:plain;">{
"development": {
"project": "DEV-1270"
},
"production": {
"project": "PROJ-1234"
}
}</pre>
<p>In our <code>.http</code> request file we are posting to endpoint <code><a href="https://ijhttp-examples.jetbrains.com/anything" class="bare">https://ijhttp-examples.jetbrains.com/anything</a></code> and we use JavaScript to read the environment variable <code>project</code>:</p>
<pre class="brush:javascript;">### POST to example URL
< {%
// Get value for environment variable 'project'.
const project = request.environment.get("project");
// Use split to get the value before and after the -.
const kindAndNumber = project.split("-");
// As an example we use the value of the environment variable
// to assign values to new request variables.
request.variables.set("kind", kindAndNumber[0]);
request.variables.set("number", kindAndNumber[1]);
%}
POST https://ijhttp-examples.jetbrains.com/anything
{
"project": "{{project}}",
"kind": "{{kind}}",
"number": "{{number}}"
}</pre>
<p>In the response we see the variables do have values:</p>
<pre class="brush:plain;">...
"json": {
"kind": "PROJ",
"number": "1234",
"project": "PROJ-1234"
}
...</pre>
<p>Written with IntelliJ IDEA 2023.3.3.</p>Hubert Klein Ikkinkhttp://www.blogger.com/profile/04670461522955162252noreply@blogger.comtag:blogger.com,1999:blog-6671019398434141469.post-55763551607204317342024-02-11T21:22:00.000+01:002024-02-11T21:22:01.020+01:00IntelliJ HTTP Client: Using In-Place Variables<p>The built-in IntelliJ HTTP Client is very useful for testing HTTP requests and responses.
If we want to define a variable in our <code>.http</code> file that is only used in this file and will not change per environment we can define it using the following syntax: <code>@<variable name> = variable value</code>.
The variable is an in-place variable and the scope of the variable in the current <code>.http</code> file.
The variable is immutable and can only be defined with a value once and cannot be overwritten.
To refer to the variable we use the syntax <code>{{<variable name>}}</code>.</p>
<p>In the following example we define a variable with the name <code>base-url</code> and the value <code><a href="https://ijhttp-example.jetbrains.com" class="bare">https://ijhttp-example.jetbrains.com</a></code>:</p>
<pre class="brush:plain;"># We define an in-place variable "base-url"
# to be used in this file.
@base-url = https://ijhttp-examples.jetbrains.com
### GET HTML page
GET {{base-url}}/html
### GET XML page
GET {{base-url}}/xml
### GET JSON page
GET {{base-url}}/json
### GET UUID4 response
GET {{base-url}}/uuid</pre>
<p>Written with IntelliJ 2023.3.3.</p>Hubert Klein Ikkinkhttp://www.blogger.com/profile/04670461522955162252noreply@blogger.comtag:blogger.com,1999:blog-6671019398434141469.post-19082249801955058552024-02-05T17:47:00.000+01:002024-02-05T17:47:00.628+01:00Gradle Goodness: Continuous Testing For Java Projects<p>The command line option <code>--continuous</code> or the short version <code>-t</code> enables Gradle’s continous build.
For a continuous build Gradle will keep on running and will re-execute the tasks we invoked if the input or of the input of one of the depended tasks has changed.
For a project with the <code>java</code> plugin we can use this option for the <code>test</code> task.
Gradle will run the <code>test</code> task and after the task has been executed Gradle will wait for any changes in the input of the task.
This means if we change our Java test code in <code>src/test/java</code> and save the source file Gradle will re-execute the <code>test</code> task and show the output.
But also if the input of other tasks changes, that the <code>test</code> task depends on, the <code>test</code> is re-executed.
So also changes in source files in our <code>src/main/java</code> directory will trigger a re-execute of the <code>test</code> task, because the <code>test</code> task depends on the <code>compileJava</code> task, and the <code>compileJava</code> task has the <code>src/main/java</code> directory as input.</p>
<p>In the following example output we invoked the <code>test</code> task with the <code>--continuous</code> option.
On the first run there was an assertion failure.
We fixed the code in <code>src/main/java</code> and saved the file.
Without having to restart Gradle we see that our assertion succeeded on the second <code>test</code> task run.</p>
<pre class="brush:plain;">$ ./gradlew --continuous test
> Task :app:test FAILED
AppTest > application has a greeting FAILED
Condition not satisfied:
result == "Hello World!!"
| |
| false
| 2 differences (84% similarity)
| Hello (w)orld!(-)
| Hello (W)orld!(!)
Hello world!
at org.example.AppTest.application has a greeting(AppTest.groovy:17)
1 test completed, 1 failed
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:test'.
> There were failing tests. See the report at: file:///Users/mrhaki/Projects/mrhaki.com/java/app/build/reports/tests/test/index.html
* Try:
> Run with --scan to get full insights.
BUILD FAILED in 3s
3 actionable tasks: 1 executed, 2 up-to-date
Waiting for changes to input files... (ctrl-d to exit)
modified: /Users/mrhaki/Projects/mrhaki.com/java/app/src/main/java/org/example/App.java
Change detected, executing build...
BUILD SUCCESSFUL in 4s
3 actionable tasks: 3 executed
Waiting for changes to input files... (ctrl-d to exit)
<=============> 100% EXECUTING [53s]
> IDLE
> IDLE</pre>
<p>To stop continuous builds we press <em>Ctrl+C</em>.</p>
<p>To have some nice output we want to use the full exception format for our test logging.
In the following example build file we configure this for our <code>test</code> task in the <code>testLogging</code> block:</p>
<pre class="brush:kotlin;">// File: build.gradle.kts
...
testing {
suites {
val test by getting(JvmTestSuite::class) {
targets {
all {
testTask.configure {
testLogging {
exceptionFormat = TestExceptionFormat.FULL
}
}
}
}
}
}
}
...</pre>
<p>Now we can run the <code>test</code> task with the <code>--continuous</code> option and work on our source files and tests in our IDE.
On each save of a source file the <code>test</code> task is executed again and we can immediately see the output of the test run.</p>
<p>Written with Gradle 8.6</p>Hubert Klein Ikkinkhttp://www.blogger.com/profile/04670461522955162252noreply@blogger.comtag:blogger.com,1999:blog-6671019398434141469.post-76549534168223880492024-02-03T10:02:00.004+01:002024-02-03T10:02:45.993+01:00Gradle Goodness: Java Toolchain Configuration Using User Defined Java Locations<p>With the java plugin we can configure a so-called Java toolchain.
The toolchain configuration is used to define which Java version needs to be used to compile and test our code in our project.
The location of the Java version can be determined by Gradle automatically.
Gradle will look at known locations based on the operating system, package managers, IntellIJ IDEA installations and Maven Toolchain configuration.</p>
<p>But we can also define the locations of our Java installations ourselves using the project property <code>org.gradle.java.installations.paths</code>.
We provide the paths to the local Java installations as a comma separated list as value for this property.
When we set this property we can also disable the Gradle toolchain detection mechanism, so only the Java installations we have defined ourselves are used.
To disable the automatic detection we set the property <code>org.gradle.java.installations.auto-detect</code> to <code>false</code>.
If we leave the value to the default value <code>true</code>, then the locations we set via <code>org.gradle.java.installations.paths</code> are added to the Java installations already found by Gradle.</p>
<p>The property <code>org.gradle.java.installations.paths</code> is a project property we can set via the command line, but we can also set it in the <code>gradle.properties</code> file in our <code>GRADLE_USER_HOME</code> directory.
Then the values we define will be used by all Gradle builds on our machine.</p>
<p>In the following example <code>gradle.properties</code> file we define the locations of two Java installations and also disable the automatic detection of Java installations. We store this file in our <code>GRADLE_USER_HOME</code> directory.</p>
<pre class="brush:plain;"># File: $GRADLE_USER_HOME/gradle.properties
# We define the locations of two Java installations on our computer.
org.gradle.java.installations.paths=C:/Users/mrhaki/tools/apps/zulu11-jdk/current,C:/Users/mrhaki/tools/apps/zulu17-jdk/current
# We disable the automatic detection of Java installations by Gradle.
org.gradle.java.installations.auto-detect=false
# We also disable the automatic download of Java installations by Gradle.
org.gradle.java.installations.auto-download=false</pre>
<p>We add the java plugin and configure our toolchain with the following Gradle build script:</p>
<pre class="brush:kotlin;">// File: build.gradle.kts
plugins {
java
}
java {
toolchain {
// We want to use Java 17 to compile, test and run our code.
// Now it doesn't matter which Java version is used by Gradle itself.
languageVersion = languageVersion.set(JavaLanguageVersion.of(17))
}
}</pre>
<p>We run the <code>javaToolchains</code> task to see the Java toolchain configuration:</p>
<pre class="brush:plain;">$ ./gradlew javaToolchains
> Task :javaToolchains
+ Options
| Auto-detection: Disabled
| Auto-download: Disabled
+ Azul Zulu JDK 11.0.22+7-LTS
| Location: C:\Users\mrhaki\tools\apps\zulu11-jdk\current
| Language Version: 11
| Vendor: Azul Zulu
| Architecture: amd64
| Is JDK: true
| Detected by: Gradle property 'org.gradle.java.installations.paths'
+ Azul Zulu JDK 17.0.10+7-LTS
| Location: C:\Users\mrhaki\tools\apps\zulu17-jdk\current
| Language Version: 17
| Vendor: Azul Zulu
| Architecture: amd64
| Is JDK: true
| Detected by: Gradle property 'org.gradle.java.installations.paths'
BUILD SUCCESSFUL in 1s
1 actionable task: 1 executed</pre>
<p>In the generated output we can see that Gradle detected the two Java installations we defined in the <code>gradle.properties</code> file using the Gradle property <code>org.gradle.java.installations.paths</code>.</p>
<p>Written with Gradle 8.5.</p>Hubert Klein Ikkinkhttp://www.blogger.com/profile/04670461522955162252noreply@blogger.comtag:blogger.com,1999:blog-6671019398434141469.post-15216682415896391602024-02-02T10:04:00.008+01:002024-02-05T08:02:43.320+01:00Gradle Goodness: Using Maven Toolchains Configuration For Gradle Java Toolchain Resolution<p>When we apply the Java plugin to our Gradle project we can configure which Java version we want to use for compiling our source code and running our tests using a <a href="https://docs.gradle.org/current/userguide/toolchains.html">toolchain configuration</a>.
The benefit of having a toolchain configuration is that we can use a different Java version for compiling and running our code than the Java version that is used by Gradle to execute the build.
Gradle will look for that Java version on our local computer or download the correct version if it is not available.
To search for a local Java installation Gradle will look for operating system specific locations, installations by package managers like <a href="https://sdkman.io">SKDMAN!</a> and <a href="https://github.com/shyiko/jabba">Jabba</a>, IntelliJ IDEA installations and <a href="https://maven.apache.org/guides/mini/guide-using-toolchains.html">Maven Toolchain</a> specifications.
Maven Toolchain specifications is an XML file describing the location of local Java installation.
Each Java installation is described by a version and optional vendor it provides and the location of the installation.
Maven uses this information to find the correct Java installation when the <code>maven-toolchain-plugin</code> is used in a Maven project.
But Gradle can also utilize Maven Toolchain specifications to find local Java installations.
This can be useful when we have to work on multiple projects where some use Maven and others use Gradle.
We can place the Maven Toolchain specification file in our Maven home directory.
This is also the default place where Gradle will look, but we can use a project property to override this location.</p>
<p>The following example shows a Maven toolchain configuration with three different Java versions:</p>
<pre class="brush:xml;"><?xml version="1.0" encoding="UTF-8"?>
<toolchains>
<toolchain>
<type>jdk</type>
<provides>
<version>11</version>
<vendor>Azul Zulu</vendor>
</provides>
<configuration>
<jdkHome>C:/Users/mrhaki/tools/apps/zulu11-jdk/current</jdkHome>
</configuration>
</toolchain>
<toolchain>
<type>jdk</type>
<provides>
<version>17</version>
<vendor>Azul Zulu</vendor>
</provides>
<configuration>
<jdkHome>C:/Users/mrhaki/apps/zulu17-jdk/current</jdkHome>
</configuration>
</toolchain>
<toolchain>
<type>jdk</type>
<provides>
<version>21</version>
<vendor>Azul Zulu</vendor>
</provides>
<configuration>
<jdkHome>C:/Users/mrhaki/tools/apps/zulu-jdk/current</jdkHome>
</configuration>
</toolchain>
</toolchains></pre>
<p>In our Gradle build file we apply the java plugin and define in the toolchain configuration we want to use Java 17 for our builds:</p>
<pre class="brush:kotlin;">// File: build.gradle.kts
plugins {
java
}
java {
toolchain {
// Use Java 17 for building and running tests
languageVersion = JavaLanguageVersion.of(17)
}
}</pre>
<p>We can now use the <code>javaToolchains</code> task to see the available Java installations:</p>
<pre class="brush:plain;">$ ./gradlew javaToolchains
> Task :javaToolchains
+ Options
| Auto-detection: Enabled
| Auto-download: Enabled
+ Azul Zulu JDK 11.0.22+7-LTS
| Location: C:\Users\mrhaki\tools\apps\zulu11-jdk\current
| Language Version: 11
| Vendor: Azul Zulu
| Architecture: amd64
| Is JDK: true
| Detected by: Maven Toolchains
+ Azul Zulu JDK 17.0.10+7-LTS
| Location: C:\Users\mrhaki\tools\apps\zulu17-jdk\current
| Language Version: 17
| Vendor: Azul Zulu
| Architecture: amd64
| Is JDK: true
| Detected by: Current JVM
+ Azul Zulu JDK 21.0.2+13-LTS
| Location: C:\Users\mrhaki\tools\apps\zulu-jdk\current
| Language Version: 21
| Vendor: Azul Zulu
| Architecture: amd64
| Is JDK: true
| Detected by: Maven Toolchains
BUILD SUCCESSFUL in 4s
1 actionable task: 1 executed</pre>
<p>The command was run using Java 17 and we can see in the output that it is detected by Gradle as the current JVM.
The Java installations for Java 11 and Java 21 are detected using Maven Toolchains.</p>
<p>If the location of the Maven Toolchain specification file is not in the default location, we can use the Gradle project property <code>org.gradle.java.installations.maven-toolchain-file</code> to specify a custom location.
We can use it from the command line using the <code>-P</code> option or we can add it to <code>gradle.properties</code> in the project root directory.</p>
<pre class="brush:plain;">$ ./gradlew javaToolchains -Porg.gradle.java.installations.maven-toolchain-file=C:/Users/mrhaki/tools/maven/toolchains.xml
...</pre>
<p>Written with Gradle 8.5.</p>Hubert Klein Ikkinkhttp://www.blogger.com/profile/04670461522955162252noreply@blogger.comtag:blogger.com,1999:blog-6671019398434141469.post-64370016660646674542024-01-13T10:45:00.004+01:002024-01-13T10:46:46.219+01:00IntelliJ HTTP Client: Allowing Insecure HTTPS Requests<p>Sometimes we want to send HTTP requests to servers that use HTTPS with self-signed certificates.
We then need to tell HTTP Client to not check the certificate of the server.
This is like running the curl command with the <code>--insecure</code> or '-k' flag.
To <a href="https://www.jetbrains.com/help/idea/http-client-in-product-code-editor.html#disable_certificate_verification">disable the certificate verification</a> for HTTP Client we need to adjust the <code>http-client.private.env.json</code> file.
For the environment we want to disable the certificate verification we must add a <code>SSLConfiguration</code> section.
In the <code>SSLConfiguration</code> section we add the <code>verifyHostCertificate</code> property with value 'true'.</p>
<p>In the following example <code>http-client.private.env.json</code> file we have the environments <code>development</code> and <code>production</code> with different passwords.
For the <code>development</code> environment we disable the certificate verification and we keep the certificate verification for the <code>production</code> environment.</p>
<pre class="brush:plain;">{
"development": {
"password": "mrhaki",
"SSLConfiguration": {
"verifyHostCertificate": false
}
},
"production": {
"password": "mrhaki42"
}
}</pre>
<p>Written with IntelliJ 2023.3.2</p>Hubert Klein Ikkinkhttp://www.blogger.com/profile/04670461522955162252noreply@blogger.comtag:blogger.com,1999:blog-6671019398434141469.post-55094943045994230132023-11-16T07:43:00.003+01:002023-11-16T07:44:13.019+01:00Spring Sweets: Spring Boot 3 With Gradle In IntelliJ<p>Spring Boot 3 requires at least Java 17, but that also means the Java version used by Gradle must also be at least 17.
Otherwise we will get the following error message when we build our Spring Boot project in IntelliJ using Gradle:</p>
<pre class="brush:plain;">A problem occurred configuring root project 'springboot'.
> Could not resolve all files for configuration ':classpath'.
> Could not resolve org.springframework.boot:spring-boot-gradle-plugin:3.1.5.
Required by:
project : > org.springframework.boot:org.springframework.boot.gradle.plugin:3.1.5
> No matching variant of org.springframework.boot:spring-boot-gradle-plugin:3.1.5 was found. The consumer was configured to find a library for use during runtime, compatible with Java 11, packaged as a jar, and its dependencies declared externally, as well as attribute 'org.gradle.plugin.api-version' with value '8.4' but:
- Variant 'apiElements' capability org.springframework.boot:spring-boot-gradle-plugin:3.1.5 declares a library, packaged as a jar, and its dependencies declared externally:
- Incompatible because this component declares a component for use during compile-time, compatible with Java 17 and the consumer needed a component for use during runtime, compatible with Java 11
- Other compatible attribute:
- Doesn't say anything about org.gradle.plugin.api-version (required '8.4')
- Variant 'javadocElements' capability org.springframework.boot:spring-boot-gradle-plugin:3.1.5 declares a component for use during runtime, and its dependencies declared externally:
- Incompatible because this component declares documentation and the consumer needed a library
- Other compatible attributes:
- Doesn't say anything about its target Java version (required compatibility with Java 11)
- Doesn't say anything about its elements (required them packaged as a jar)
- Doesn't say anything about org.gradle.plugin.api-version (required '8.4')
- Variant 'mavenOptionalApiElements' capability org.springframework.boot:spring-boot-gradle-plugin-maven-optional:3.1.5 declares a library, packaged as a jar, and its dependencies declared externally:
- Incompatible because this component declares a component for use during compile-time, compatible with Java 17 and the consumer needed a component for use during runtime, compatible with Java 11
- Other compatible attribute:
- Doesn't say anything about org.gradle.plugin.api-version (required '8.4')
- Variant 'mavenOptionalRuntimeElements' capability org.springframework.boot:spring-boot-gradle-plugin-maven-optional:3.1.5 declares a library for use during runtime, packaged as a jar, and its dependencies declared externally:
- Incompatible because this component declares a component, compatible with Java 17 and the consumer needed a component, compatible with Java 11
- Other compatible attribute:
- Doesn't say anything about org.gradle.plugin.api-version (required '8.4')
- Variant 'runtimeElements' capability org.springframework.boot:spring-boot-gradle-plugin:3.1.5 declares a library for use during runtime, packaged as a jar, and its dependencies declared externally:
- Incompatible because this component declares a component, compatible with Java 17 and the consumer needed a component, compatible with Java 11
- Other compatible attribute:
- Doesn't say anything about org.gradle.plugin.api-version (required '8.4')
- Variant 'sourcesElements' capability org.springframework.boot:spring-boot-gradle-plugin:3.1.5 declares a component for use during runtime, and its dependencies declared externally:
- Incompatible because this component declares documentation and the consumer needed a library
- Other compatible attributes:
- Doesn't say anything about its target Java version (required compatibility with Java 11)
- Doesn't say anything about its elements (required them packaged as a jar)
- Doesn't say anything about org.gradle.plugin.api-version (required '8.4')
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.</pre>
<p>The issue is that the Spring Boot Gradle plugin 3.1.5 requires Java 17, but our project is using Java 11.
We can fix this by explicitly setting the Java version that Gradle uses in IntelliJ.
Go to <i>Settings > Build, Execution, Deployment > Build Tools > Gradle</i> and change the JVM used for Gradle to a JDK version of at least version 17.</p>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgq30dc25ngOui5MJjQ7E4mvGejYpHia1BKXjIENPJtad0OF8wtLtUv6ETy6qAP9okumQx3BMQ9wLzLRaMCn-I42znZTqXDpLGocD7qITdn4QVw0QMG5X5mePMsPPSh4bxmVyK9TR-n6o_xpxEgFxy-Q_JYJpuJ5qruTlcNcd9saOPd19lhu_Y0ozh0QAgg/s1600/intellij-gradle-springboot.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="828" data-original-width="1094" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgq30dc25ngOui5MJjQ7E4mvGejYpHia1BKXjIENPJtad0OF8wtLtUv6ETy6qAP9okumQx3BMQ9wLzLRaMCn-I42znZTqXDpLGocD7qITdn4QVw0QMG5X5mePMsPPSh4bxmVyK9TR-n6o_xpxEgFxy-Q_JYJpuJ5qruTlcNcd9saOPd19lhu_Y0ozh0QAgg/s1600/intellij-gradle-springboot.png"/></a></div>
<p>Written with Spring Boot 3.1.5 and IntelliJ 2023.2.4.</p>Hubert Klein Ikkinkhttp://www.blogger.com/profile/04670461522955162252noreply@blogger.comtag:blogger.com,1999:blog-6671019398434141469.post-54295346427623927692023-11-05T17:14:00.001+01:002023-11-05T17:14:24.024+01:00IntelliJ HTTP Client: Re-using Javascript In Pre-Request And Response Handlers<p>When we use the IntelliJ HTTP Client we can write Javascript for the pre-request and response handlers.
The Javascript code must be in between <code>{% …​ %}</code> delimeters.
If we want to re-use Javascript functions in the pre-request or response handlers we can store them in an external Javascript file.
Then we use the <code>import</code> statement to import either the whole file or specify explicitly the code we want to import.
This way we can reuse code for different pre-request and response handlers.</p>
<p>In the following example we import the <code>createUsername</code> function from the external <code>scripts/username.js</code> file and use it in the pre-request handler.
In the response handler we import the external file <code>scripts/validate-200-response.js</code> and the code from the file is executed when the response is available.</p>
<pre class="brush:jscript;">// File: scripts/username.js
function createUsername() {
const usernames = ["mrhaki", "hubert"];
const randomIndex = (Math.floor(Math.random() * 11) % 2);
// Return mrhaki or hubert as username.
return usernames[randomIndex];
}
export {createUsername};</pre>
<pre class="brush:jscript;">// File: scripts/validate-200-response.js
client.test("Response is OK", function () {
client.assert(response.status === 200);
});</pre>
<pre class="brush:plain;">### POST JSON payload to /anything
< {%
// Refer to createUsername function from external Javascript file.
import {createUsername} from './scripts/username';
// Use function createUsername.
request.variables.set("username", createUsername());
%}
POST https://ijhttp-examples.jetbrains.com/anything
{
"username": "{{username}}"
}
> {%
// Validate 200 response from external file.
import './scripts/validate-200-response';
client.test("Response has username set", function () {
client.log("Username -> " + response.body.json.username);
client.assert(["mrhaki", "hubert"].includes(response.body.json.username));
});
%}</pre>
<p>Written with IntelliJ 2023.2.4.</p>Hubert Klein Ikkinkhttp://www.blogger.com/profile/04670461522955162252noreply@blogger.comtag:blogger.com,1999:blog-6671019398434141469.post-43425518069700505502023-11-05T15:48:00.001+01:002023-11-05T15:48:38.243+01:00IntelliJ HTTP Client: Using External Files As JSON Payload<p>The built-in IntelliJ HTTP Client is very useful for testing HTTP requests and responses.
We can use it to test for example a REST API that works with JSON data.
If an endpoint expects a JSON payload we can specify the payload in our HTTP Client request file.
But if we have a lot of endpoints and large payload the request file can get big and messy.
Instead of having the payload in the request file directly we can specify an external JSON file with the payload and use it for a request body.
We must use the <code><</code> operator and give the name of the file with our JSON payload.
The IntelliJ HTTP Client will read the contents of that file and use it as the request body.
The payload may also contain (dynamic) variables and those variables will be replaced with correct values when the request is executed.</p>
<p>In the following example we have a POST request with a JSON payload from an external file.
The payload in the file contains variables.
We use an endpoint from the test API at <a href="https://ijhttp-examples.jetbrains.com" class="bare">https://ijhttp-examples.jetbrains.com</a> that expects a JSON payload.
The response will contain the payload with request payload where all variables are replaced in the <code>json</code> property.</p>
<pre class="brush:plain;">### POST to /anything with payload from external file
POST https://ijhttp-examples.jetbrains.com/anything
Content-Type: application/json
< ./sample-payload.json
# Content of sample-playload.json:
#{
# "id": "{{$random.uuid}}",
# "username": "{{username}}",
#}
# Content of http-client.env.json with value for variable username:
#{
# "test": {
# "username": "mrhaki"
# }
#}
> {%
client.test("Response is OK", function () {
client.assert(response.status === 200);
});
client.test("Response has username set", function () {
client.assert(response.body.json.username === "mrhaki");
});
client.test("Response has id with value", function () {
const regexExp = /^[0-9a-f]{8}\b-[0-9a-f]{4}\b-[0-9a-f]{4}\b-[0-9a-f]{4}\b-[0-9a-f]{12}$/gi;
client.assert(regexExp.test(response.body.json.id));
});
%}</pre>
<p>Written with IntelliJ IDEA 2023.2.4.</p>Hubert Klein Ikkinkhttp://www.blogger.com/profile/04670461522955162252noreply@blogger.comtag:blogger.com,1999:blog-6671019398434141469.post-52284644047026519992023-10-23T14:24:00.002+02:002023-10-28T07:14:46.355+02:00jq Joy: Using String Interpolation<p><a href="https://jqlang.github.io/jq/"><code>jq</code></a> is a powerful tool to work with JSON from the command-line.
The tool has a lot of functions that makes our live easier.
With <code>jq</code> we can use expressions in strings that will be evaluated and inserted into the string value.
This is called <em>string interpolation</em>.
The expression is enclosed by parentheses and the first parenthesis is prefixed with a backslash: <code>\(<expression>)</code>.
The expression can be any valid <code>jq</code> expression and the result of the expression will be inserted into the string.</p>
<p>In the following example we use string interpolation to print a greeting message and we use a simple property reference, but also a bit more complicated expressions:</p>
<pre class="brush:plain;">$ jq --raw-output --null-input '{"prefix": "Hi", "name": "mrhaki", "visitors": 41} | "\(.prefix),\nwelcome \(.name | ascii_upcase)\nYou are visitor \(.visitors + 1)!"'
Hi,
welcome MRHAKI
You are visitor 42!</pre>
<p>Written with jq 1.7.</p>Hubert Klein Ikkinkhttp://www.blogger.com/profile/04670461522955162252noreply@blogger.comtag:blogger.com,1999:blog-6671019398434141469.post-10098003242300617512023-10-09T19:50:00.004+02:002023-10-09T19:50:55.155+02:00Groovy Goodness: Using NullCheck Annotation To Prevent NullPointerException<p>In Groovy we can apply the <code>@NullCheck</code> annotation to a class, constructor or method.
The annotation is an AST (Abstract Syntax Tree) transformation and will insert code that checks for null values in methods or constructors.
If a <code>null</code> value is passed to an annotated method or constructor, it will throw an <code>IllegalArgumentException</code>.
Without the annotation we could have a <code>NullPointerException</code> if we try to invoke a method on the value we pass as argument.
The annotation has an optional property <code>includeGenerated</code> which by default is <code>false</code>.
If we set it to <code>true</code> then the null checks are also applied to generated methods and constructors.
This is very useful if we apply other AST transformations to our class that generates additional code.</p>
<p>In the following example we use the <code>@NullCheck</code> annotation for a method and at class level:</p>
<pre class="brush:groovy;">import groovy.transform.NullCheck
@NullCheck
String upper(String value) {
"Upper:" + value.toUpperCase()
}
assert upper("groovy") == "Upper:GROOVY"
try {
upper(null)
} catch (IllegalArgumentException e) {
assert e.message == "value cannot be null"
}</pre>
<pre class="brush:groovy;">import groovy.transform.NullCheck
// Apply null check for all constructors and methods.
@NullCheck
class Language {
private String name
Language(String name) {
this.name = name;
}
String upper(String prefix) {
return prefix + name.toUpperCase();
}
}
def groovy = new Language("groovy")
assert groovy.upper("Upper:") == "Upper:GROOVY"
// Method arguments are checked.
try {
groovy.upper(null)
} catch (IllegalArgumentException e) {
assert e.message == "prefix cannot be null"
}
// Constructor argument is also checked.
try {
def lang = new Language(null)
} catch (IllegalArgumentException e) {
assert e.message == "name cannot be null"
}</pre>
<p>In the following example we set the <code>includeGenerated</code> property to true to also generate null checks for generated code like the constructor generated by <code>@TupleConstructor</code>:</p>
<pre class="brush:groovy;">import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@NullCheck(includeGenerated = true)
@TupleConstructor
class Language {
final String name
String upper(String prefix) {
return prefix + name.toUpperCase();
}
}
// Constructor is generated by @TupleConstructor and
// @NullCheck is applied to the generated constructor.
try {
def lang = new Language(null)
} catch (IllegalArgumentException e) {
assert e.message == "name cannot be null"
}</pre>
<p>Written with Groovy 4.0.13</p>Hubert Klein Ikkinkhttp://www.blogger.com/profile/04670461522955162252noreply@blogger.comtag:blogger.com,1999:blog-6671019398434141469.post-35346258070487093622023-10-09T07:27:00.002+02:002023-10-10T06:36:21.526+02:00jq Joy: Using Default Values With The Alternative Operator<p><a href="https://jqlang.github.io/jq/"><code>jq</code></a> is a powerful tool to work with JSON from the command-line.
The tool has a lot of functions and operators that makes our live easier.
One of the operators we can use is the <a href="https://jqlang.github.io/jq/manual/v1.7/#alternative-operator">alternative operator</a> <code>//</code> which allows us to specify default values.
If the value on the left side of the operator <code>//</code> is empty, null or false then the value on the right side is returned, otherwise the value itself is returned.
The operator can be used multiple times if we want to have multiple fallbacks for a value we are checking.</p>
<p>In the following examples we use the <code>//</code> operator in different scenarios:</p>
<pre class="brush:plain;">$ jq --null-input '[42, "jq joy", null, false] | [.[] | . // "default"]'
[
42,
"jq joy",
"default",
"default"
]</pre>
<pre class="brush:plain;">$ jq --null-input 'empty // "value"'
"value"</pre>
<p>We can chain the <code>//</code> operator to specify multiple fallback options:</p>
<pre class="brush:plain;">$ jq --null-input '{"name": "mrhaki"} | {"alias": (.username // .user // "not available") }'
{
"alias": "not available"
}
$ jq --null-input '{"user": "mrhaki"} | {"alias": (.username // .user // "not available") }'
{
"alias": "mrhaki"
}</pre>
<p>Written with jq 1.7.</p>Hubert Klein Ikkinkhttp://www.blogger.com/profile/04670461522955162252noreply@blogger.comtag:blogger.com,1999:blog-6671019398434141469.post-77942040347342716072023-10-05T16:22:00.005+02:002023-10-10T06:32:08.854+02:00jq Joy: Reverse An Array<p><a href="https://jqlang.github.io/jq/"><code>jq</code></a> is a powerful tool to work with JSON from the command-line.
The tool has a lot of functions that makes our live easier.
To simply reverse the order of elements in an array we can use the <code>reverse</code> function.
The values in the input array are reversed and returned in the output array.</p>
<p>In the following example we use the <code>reverse</code> function for an array:</p>
<pre class="brush:plain;">$ jq --null-input --compact-output '[1, 2, 3, 4, 5] | reverse'
[5,4,3,2,1]</pre>
<p>Written with jq 1.7.</p>Hubert Klein Ikkinkhttp://www.blogger.com/profile/04670461522955162252noreply@blogger.comtag:blogger.com,1999:blog-6671019398434141469.post-91668622813070471152023-10-04T22:10:00.001+02:002023-10-10T06:33:32.552+02:00jq Joy: Checking String Ends Or Starts With Given String<p><a href="https://jqlang.github.io/jq/"><code>jq</code></a> is a powerful tool to work with JSON from the command-line.
The tool has a lot of functions that makes our live easier.
We can check if a string starts or ends with a given string using the <code>startswith</code> and <code>endswith</code> functions.
We pass the string value we want to see a string value starts with or ends with as the argument to the functions.
The function returns <code>true</code> if the string starts or ends with the given string and <code>false</code> otherwise.</p>
<p>In the following examples we use both functions to check some string values in an array:</p>
<pre class="brush:plain;">$ jq --null-input '["mrhaki", "hubert", "MrHaki"] | [.[] | startswith("mr") or startswith("Mr")]'
[
true,
false,
true
]</pre>
<pre class="brush:plain;">$ jq --null-input '["jq!", "JSON"] | [.[] | endswith("!")]'
[
true,
false
]</pre>
<p>Written with jq 1.7.</p>Hubert Klein Ikkinkhttp://www.blogger.com/profile/04670461522955162252noreply@blogger.comtag:blogger.com,1999:blog-6671019398434141469.post-67873128084503313212023-10-03T14:42:00.006+02:002023-10-11T05:59:25.863+02:00jq Joy: Sum Of Elements In An Array Or Object<p><a href="https://jqlang.github.io/jq/"><code>jq</code></a> is a powerful tool to work with JSON from the command-line.
The tool has a lot of functions that makes our live easier.
One of the functions is <code>add</code> which adds all elements in an array or values in an object.
The function has no arguments.
The elements in an array are added together if they are numbers and concatenated if they are strings.
If the input is an object then the values are added together.
When the input is an empty array or object then null is returned.</p>
<p>In the following examples we see different usages of the <code>add</code> function:</p>
<pre class="brush:plain;">$ jq --null-input '[1, 2, 3, 4,] | add'
10</pre>
<pre class="brush:plain;">$ jq --null-input '["a", "b", "c", "d"] | add'
"abcd"</pre>
<pre class="brush:plain;">$ jq --null-input '{ "burger": 3.23, "drinks": 2.89 } | add'
6.12</pre>
<pre class="brush:plain;">$ jq --null-input '[] | add'
null</pre>
<p>Written with jq 1.7.</p>Hubert Klein Ikkinkhttp://www.blogger.com/profile/04670461522955162252noreply@blogger.comtag:blogger.com,1999:blog-6671019398434141469.post-87821733909617359552023-10-03T09:56:00.001+02:002023-10-10T06:39:32.291+02:00jq Joy: Flatten Arrays<p><a href="https://jqlang.github.io/jq/"><code>jq</code></a> is a powerful tool to work with JSON from the command-line.
The tool has a lot of functions that makes our live easier.
We can use the <code>flatten</code> function to flatten nested arrays into a single array.
If we use the function without an argument the arrays are flattened recursively, resulting in a flat array with all elements.
But we can also set the depth we want to flatten the array by passing an integer argument to the <code>flatten</code> function.</p>
<p>In the following example we use the <code>flatten</code> function without arguments to recursively flatten the array:</p>
<pre class="brush:plain;">$ jq --null-input '[1, [2, 3], [[4]], 5] | flatten'
[
1,
2,
3,
4,
5
]</pre>
<p>We can pass an argument to the <code>flatten</code> function to indicate the depth to flatten the collection.
In the following example we want to flatten only one level deep:</p>
<pre class="brush:plain;">$ jq --null-input '[1, [2, 3], [[4]], 5] | flatten(1)'
[
1,
2,
3,
[
4
],
5
]</pre>
<p>Written with jq 1.7.</p>Hubert Klein Ikkinkhttp://www.blogger.com/profile/04670461522955162252noreply@blogger.comtag:blogger.com,1999:blog-6671019398434141469.post-42635703574821738772023-10-02T11:27:00.001+02:002023-10-02T11:34:38.760+02:00Awesome AssertJ: Using A Custom Representation For Objects<p>The assertion error messages from AssertJ will use the <code>toString()</code> method of an object to give more insight about why the assertion could have failed.
If an object doesn’t override the <code>toString()</code> method the default implementation is <code>Object#toString()</code>.
The default implementation will print out the class name and an hexadecimal value of <code>hashCode</code> separated by a <code>@</code>.
This doesn’t give much information about the actual object.
For classes we have control over we can always implement a <code>toString()</code> method, but for third party classes we may not be able to do that.
In order to customize how an object is represented in assertion error messages, AssertJ allows us to provide a custom representation class for an object.
The custom representation class must implement the <code>org.assertj.core.presentation.Representation</code> interface.
The interface has one method <code>String toStringOf(Object)</code> that should return a String representation of the object.
If we want to keep the default behavior for other classes and only override it for our own class, we can extend the <code>StandardRepresentation</code> class and override the method <code>String fallbackToStringOf(Object)</code>.</p>
<p>In the following example we provide a custom representation for the <code>User</code> class that will print the <code>username</code> property in the error messages instead of the default <code>toString()</code> implementation:</p>
<pre class="brush:java;">package mrhaki;
import org.assertj.core.api.Assertions;
import org.assertj.core.presentation.StandardRepresentation;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class CustomRepresentation {
// Helper class used in tests.
private static class User {
private final String username;
private User(String username) {
this.username = username;
}
}
// Custom representation for the User class.
// Here we can write how we want to represent the User class in
// assertion error messages.
private static class UserRepresentation extends StandardRepresentation {
/**
* The User class doesn't have a toString implementation, so we write
* custom code so in assertion error message our object is nicely formatted.
*
* @param object the object to represent (never {@code null}
* @return Custom string representation of User or standard representation for other objects.
*/
@Override
protected String fallbackToStringOf(Object object) {
if (object instanceof User user) {
return "User(username=%s)".formatted(user.username);
}
return super.fallbackToStringOf(object);
}
}
@Test
void assertUserObject() {
final User mrhaki = new User("mrhaki");
try {
// assert will fail and the assertion error will contain
// a default representation of the objects.
assertThat(mrhaki).isEqualTo(new User("mrhaki42"));
} catch (AssertionError e) {
// expected: mrhaki.CustomRepresentation$User@126253fd
// but was: mrhaki.CustomRepresentation$User@7eecb5b8
assertThat(e).hasMessageContaining("expected: mrhaki.CustomRepresentation$User@")
.hasMessageContaining("but was: mrhaki.CustomRepresentation$User@");
}
}
@Test
void asserUserObjectWithCustomRepresentation() {
final User mrhaki = new User("mrhaki");
try {
// assert will fail and the assertion error will contain
// a custom representation of the objects only for this assert.
assertThat(mrhaki).withRepresentation(new UserRepresentation()).isEqualTo(new User("mrhaki42"));
} catch (AssertionError e) {
// expected: User(username=mrhaki42)
// but was: User(username=mrhaki)
assertThat(e).hasMessageContaining("expected: User(username=mrhaki42)")
.hasMessageContaining("but was: User(username=mrhaki)");
}
}
@Test
void asserUserObjectWithCustomRepresentation2() {
final User mrhaki = new User("mrhaki");
try {
// Set custom representation for User objects for all asserts.
// This can also be defined at class level.
Assertions.useRepresentation(new UserRepresentation());
// assert will fail and the assertion error will contain
// a custom representation of the objects.
assertThat(mrhaki).isEqualTo(new User("mrhaki42"));
} catch (AssertionError e) {
// expected: User(username=mrhaki42)
// but was: User(username=mrhaki)
assertThat(e).hasMessageContaining("expected: User(username=mrhaki42)")
.hasMessageContaining("but was: User(username=mrhaki)");
} finally {
// Reset custom representation.
Assertions.useDefaultRepresentation();
}
}
@Test
void asserUserObjectWithCustomRepresentationSAM() {
final User mrhaki = new User("mrhaki");
try {
// assert will fail and the assertion error will contain
// a custom representation of the objects for this assert only.
assertThat(mrhaki).withRepresentation(new UserRepresentation()).isEqualTo(new User("mrhaki42"));
} catch (AssertionError e) {
// expected: User(username=mrhaki42)
// but was: User(username=mrhaki)
assertThat(e).hasMessageContaining("expected: User(username=mrhaki42)")
.hasMessageContaining("but was: User(username=mrhaki)");
}
}
@Test
void asserUserObjectWithCustomRepresentationSAM() {
final User mrhaki = new User("mrhaki");
try {
// assert will fail and the assertion error will contain
// a custom representation of the objects implemented
// with the Representation interface as single abstract method (SAM).
assertThat(mrhaki).withRepresentation(obj -> {
if (obj instanceof User user) {
return "[User:username=%s]".formatted(user.username);
} else {
return Objects.toIdentityString(obj);
}
}).isEqualTo(new User("mrhaki42"));
} catch (AssertionError e) {
// expected: [User:username=mrhaki42]
// but was: [User:username=mrhaki]
assertThat(e).hasMessageContaining("expected: [User:username=mrhaki42]")
.hasMessageContaining("but was: [User:username=mrhaki]");
}
}
}</pre>
<p>Written with AssertJ 3.24.2</p>Hubert Klein Ikkinkhttp://www.blogger.com/profile/04670461522955162252noreply@blogger.comtag:blogger.com,1999:blog-6671019398434141469.post-3684295333756479922023-09-26T16:58:00.004+02:002023-10-10T06:42:31.331+02:00jq Joy: Deleting Keys From An Object<p><a href="https://jqlang.github.io/jq/"><code>jq</code></a> is a powerful tool to work with JSON from the command-line.
The tool has a lot of functions that makes our live easier.
If we want to delete keys from an object we can use the <code>del</code> function and pass the key name as argument.
The argument is actually a path expression so we can refer to a key name with <code>.key</code>, but also use jq expressions.</p>
<p>In the following examples we use the <code>del</code> function in several use cases:</p>
<pre class="brush:plain;">$ jq --null-input '{
"username": "mrhaki",
"firstName": "Hubert",
"lastName": "Klein Ikkink"
} | del(.username)'
{
"firstName": "Hubert",
"lastName": "Klein Ikkink"
}</pre>
<pre class="brush:plain;">$ jq --null-input '{
"username": "mrhaki",
"firstName": "Hubert",
"lastName": "Klein Ikkink"
} | del(.username) | del(.lastName)'
{
"firstName": "Hubert"
}</pre>
<p>If we want to remove multiple keys at once we can pass multiple key names separated by comma to the <code>del</code> function:</p>
<pre class="brush:plain;">$ jq --null-input '{
"username": "mrhaki",
"firstName": "Hubert",
"lastName": "Klein Ikkink"
} | del(.username, .lastName)'
{
"firstName": "Hubert"
}</pre>
<p>We can use also jq path expressions for example to delete keys by index instead of key name:</p>
<pre class="brush:plain;">$ jq --null-input '{
"username": "mrhaki",
"firstName": "Hubert",
"lastName": "Klein Ikkink"
} | del(.[keys_unsorted | .[0,2]])'
{
"firstName": "Hubert"
}</pre>
<p>Written with jq 1.7.</p>Hubert Klein Ikkinkhttp://www.blogger.com/profile/04670461522955162252noreply@blogger.comtag:blogger.com,1999:blog-6671019398434141469.post-76305779797521618202023-09-26T16:22:00.002+02:002023-10-26T07:11:35.276+02:00jq Joy: Getting Keys From Object And Indices From Array<p><a href="https://jqlang.github.io/jq/"><code>jq</code></a> is a powerful tool to work with JSON from the command-line.
The tool has a lot of functions that makes our live easier.
For example we can use the <code>keys</code> and <code>keys_unsorted</code> functions to get the keys from an object.
The function <code>keys</code> will return the keys in sorted order while <code>keys_unsorted</code> will return them in the original order from the object.
With the same functions we can also get the indices of the elements in an array, but there is no sorting involved, so both functions return the same output.</p>
<p>In the following examples we first use <code>keys</code> and then <code>keys_unsorted</code> to get the keys from an object:</p>
<pre class="brush:plain;">$ jq --null-input '{
"username": "mrhaki",
"firstName": "Hubert",
"lastName": "Klein Ikkink"
} | keys'
[
"firstName",
"lastName",
"username"
]</pre>
<pre class="brush:plain;">$ jq --null-input '{
"username": "mrhaki",
"firstName": "Hubert",
"lastName": "Klein Ikkink"
} | keys_unsorted'
[
"username",
"firstName",
"lastName"
]</pre>
<p>To get the indices of an array we can use the <code>keys</code> function as well:</p>
<pre class="brush:plain;">$ jq --null-input '[1, 10, 100] | keys'
[
0,
1,
2
]</pre>
<p>Written with jq 1.7.</p>Hubert Klein Ikkinkhttp://www.blogger.com/profile/04670461522955162252noreply@blogger.comtag:blogger.com,1999:blog-6671019398434141469.post-17558088191179608032023-07-22T12:03:00.004+02:002023-07-22T12:03:46.828+02:00Java Joy: Using mapMulti Method Of The Stream API<p>Since Java 16 we can use the method <code>mapMulti(BiConsumer)</code> of the <code>Stream</code> API.
This method allows us to map each element of the stream to multiple elements.
We can also do that with the <code>flatMap(Function)</code> method, but if we want to map a limited set of elements, <code>mapMulti</code> is more convenient.
Internally a shared stream is used and we don’t have the cost of creating a new stream for each element.
Another use case is if the logic to map an element to multiple elements is complex and is hard to implement by returning a stream.
Then <code>mapMulti</code> allows us to write that logic in a <code>BiConsumer</code> instead of a <code>Function</code>.</p>
<p>In the following code we use the <code>mapMulti</code> method in several examples:</p>
<pre class="brush:java;">package mrhaki.stream;
import javax.print.attribute.HashPrintServiceAttributeSet;
import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class MapMulti {
public static void main(String[] args) {
// We want to return a stream of string values
// and the uppercase variant
// if the original element has the letter o.
assert Stream.of("Java", "Groovy", "Clojure")
.mapMulti((language, downstream) -> {
if (language.contains("o")) {
downstream.accept(language);
downstream.accept(language.toUpperCase());
}
})
.toList()
.equals(List.of("Groovy", "GROOVY", "Clojure", "CLOJURE"));
// Same logic implemented with flatMap.
assert Stream.of("Java", "Groovy", "Clojure")
.filter(language -> language.contains("o"))
.flatMap(language -> Stream.of(language, language.toUpperCase()))
.toList()
.equals(List.of("Groovy", "GROOVY", "Clojure", "CLOJURE"));
// Helper record to store a name and set of language names.
record Developer(String name, List<String> languages) {}
// Number of sample developers that work with different languages.
var hubert = new Developer("mrhaki", List.of("Java", "Groovy", "Clojure"));
var java = new Developer("java", List.of("Java"));
var clojure = new Developer("clojure", List.of("Clojure"));
var groovy = new Developer("groovy", List.of("Groovy"));
record Pair(String name, String language) {}
// Let's find all developers that have Java in their
// set of languages and return a new Pair
// object with the name of the developer and a language.
assert Stream.of(hubert, java, clojure, groovy)
// We can explicitly state the class that will be
// in the downstream of the compiler cannot
// deduct it using a <...> syntax.
.<Pair>mapMulti((developer, downstream) -> {
var languages = developer.languages();
if (languages.contains("Java")) {
for (String language : developer.languages()) {
downstream.accept(new Pair(developer.name(), language));
}
}
})
.toList()
.equals(List.of(new Pair("mrhaki", "Java"),
new Pair("mrhaki", "Groovy"),
new Pair("mrhaki", "Clojure"),
new Pair("java", "Java")));
// Same logic using filter and flatMap.
assert Stream.of(hubert, java, clojure, groovy)
.filter(developer -> developer.languages().contains("Java"))
.flatMap(developer -> developer.languages()
.stream()
.map(language -> new Pair(developer.name(), language)))
.toList()
.equals(List.of(new Pair("mrhaki", "Java"),
new Pair("mrhaki", "Groovy"),
new Pair("mrhaki", "Clojure"),
new Pair("java", "Java")));
// We want to expand each number to itself and its square root value
// and we muse mapMultiToInt here.
var summaryStatistics = Stream.of(1, 2, 3)
.mapMultiToInt((number, downstream) -> {
downstream.accept(number);
downstream.accept(number * number);
})
.summaryStatistics();
assert summaryStatistics.getCount() == 6;
assert summaryStatistics.getSum() == 20;
assert summaryStatistics.getMin() == 1;
assert summaryStatistics.getMax() == 9;
}
}</pre>
<p>Written with Java 20.</p>Hubert Klein Ikkinkhttp://www.blogger.com/profile/04670461522955162252noreply@blogger.comtag:blogger.com,1999:blog-6671019398434141469.post-40164307620752574722023-07-17T06:33:00.003+02:002023-07-20T06:03:17.313+02:00Awesome AssertJ: Writing Assertions For Optional<p>For a lot of types AssertJ has special assertion methods.
Also for the type <code>Optional</code>.
If we want to assert an <code>Optional</code> value we can use several methods that AssertJ provides.
For example to check if an <code>Optional</code> is present we can use <code>isPresent()</code> or the alias <code>isNotEmpty()</code>.
To check if the <code>Optional</code> is empty we can use <code>isEmpty()</code> or the alias <code>isNotPresent()</code>.
Checking the value of an <code>Optional</code> (if it is indeed set) can be done with <code>hasValue()</code> or <code>contains()</code>.
For more fine grained assertions on the value we can use <code>hasValueSatisfying(Condition)</code> or <code>hasValueSatisfying(Consumer)</code>.
With the <code>map(Function)</code> and <code>flatMap(Function)</code> methods we can map the <code>Optional</code>, if not empty, to another value and assert that value.</p>
<p>In the following example we see assertion methods for an <code>Optional</code> instance:</p>
<pre class="brush:java;">package mrhaki;
import org.assertj.core.api.Condition;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
class OptionalAssertions {
@Test
void checkOptionalPresent(){
assertThat(Optional.of("Awesome AssertJ"))
// Check if Optional has a value.
.isPresent()
// Or we use the alias isNotEmpty().
.isNotEmpty();
}
@Test
void checkOptionalNotPresent() {
assertThat(Optional.empty())
// Check if Optional is empty.
.isEmpty()
// Or we use the aliase isNotPresent().
.isNotPresent();
}
@Test
void checkValueOfOptional() {
assertThat(Optional.of("Awesome AssertJ"))
// We can check the value with hasValue(...).
// We don't have to call get() ourselves.
.hasValue("Awesome AssertJ")
// Or we use the alias contains(...).
.contains("Awesome AssertJ");
}
@Test
void checkGetOfOptional() {
// We can use get() to chain more assertions,
// but we can only use generic Object assertions.
assertThat(Optional.of("Awesome AssertJ"))
.get()
// We can only use Object assertions.
.isEqualTo("Awesome AssertJ");
// If we use get(InstanceOfAssertFactory) we get
// back an object to use assertion methods for the
// given type.
// The interface InstanceOfAssertFactories has a lot of
// useful constants to force the type returned by the Optional,
// so we can use the assertions for that type.
assertThat(Optional.of("Awesome AssertJ"))
.get(InstanceOfAssertFactories.STRING)
// Now we can use String assertions:
.startsWith("Awesome")
.endsWith("AssertJ");
}
@Test
void checkValueSatisfyingOfConditional() {
// We can use hasValueSatisfying(Consumer)
// with the instance that is wrapped in the Optional
// for more fine grained assertion.
// Also, we can use assertions for the type wrapped
// in the Optional.
assertThat(Optional.of("Awesome AssertJ"))
.hasValueSatisfying(s -> {
assertThat(s)
.startsWith("Awesome")
.endsWith("AssertJ");
});
}
@Test
void checkObjectValueOfOptionalWithCondition() {
// Record to store properties of a framework.
record Framework(String name, boolean awesome) {}
// We can write a Condition that checks if the awesome
// property is set to true.
var isAwesomeFramework = new Condition<Framework>(Framework::awesome, "Framework is awesome!");
// And with hasValueSatisfying we can use the Condition.
assertThat(Optional.of(new Framework("AssertJ", true)))
.hasValueSatisfying(isAwesomeFramework);
}
@Test
void checkTransformedValueOfOptional() {
// We can use map(...) to transform the value
// of the optional (if present)
// and write assertions for the transformed value.
assertThat(Optional.of("Awesome AssertJ"))
.map(String::toUpperCase)
.hasValue("AWESOME ASSERTJ");
// Record to store framework data with an optional type.
record Framework(String name, Optional<Boolean> awesome) {}
// With flatMap(...) we can transform the value
// of the optional (if present) to another Optional
// and write assertions for this transformed Optinal value.
assertThat(Optional.of(new Framework("Awesome AssertJ", Optional.of(true))))
.flatMap(Framework::awesome)
.isPresent()
.hasValue(true);
}
@Test
void checkInstanceOfOptional() {
// With containsInstanceOf we can write an assertion
// to check the type that is contained in the Optional.
assertThat(Optional.of("Awesome AssertJ"))
.containsInstanceOf(String.class);
}
}</pre>
<p>Written with AssertJ 3.24.2.</p>Hubert Klein Ikkinkhttp://www.blogger.com/profile/04670461522955162252noreply@blogger.com