Skip to main content

2024-10-18

One-liner

"We are students, and students should act like students." --- JoJo's Bizarre Adventure · YourHouseExploded


Java String

Compared to Java 11, Java 21 introduces many new features and methods for String operations, making string manipulation more convenient and intuitive. Below are the key new features and methods related to String introduced in Java 21:

1. stripIndent() (Java 13)

  • Purpose: Removes common leading spaces from each line in a multi-line string, commonly used with text blocks.
  • Example:
    String text = """
    Hello,
    World!
    """;
    System.out.println(text.stripIndent());
    Output:
    Hello,
    World!

2. translateEscapes() (Java 15)

  • Purpose: Converts escape sequences in a string to their actual characters, such as converting \\n to a newline.
  • Example:
    String str = "Hello\\nWorld";
    System.out.println(str.translateEscapes());
    Output:
    Hello
    World

3. formatted() (Java 15)

  • Purpose: Formats a string, similar to String.format(), but more concise.
  • Example:
    String name = "John";
    String greeting = "Hello, %s".formatted(name);
    System.out.println(greeting);
    Output:
    Hello, John

4. indent(int n) (Java 12)

  • Purpose: Adds or removes a specified number of spaces of indentation to each line. Positive values add indentation, negative values remove indentation.
  • Example:
    String text = "Hello\nWorld";
    System.out.println(text.indent(4));
    Output:
        Hello
    World

5. repeat(int count) (Java 11)

  • Purpose: Repeats the current string a specified number of times.
  • Example:
    String str = "Hello ";
    System.out.println(str.repeat(3));
    Output:
    Hello Hello Hello 

6. isBlank() (Java 11)

  • Purpose: Checks if a string is empty or contains only whitespace.
  • Example:
    String str = "   ";
    System.out.println(str.isBlank()); // true

7. lines() (Java 11)

  • Purpose: Splits the string into a stream (Stream) of lines.
  • Example:
    String text = "Hello\nWorld\nJava";
    text.lines().forEach(System.out::println);
    Output:
    Hello
    World
    Java

8. strip(), stripLeading(), stripTrailing() (Java 11)

  • Purpose: Removes leading and trailing whitespace from strings. strip() uses the Unicode standard, making it more robust than trim().
  • Example:
    String str = "   Hello World   ";
    System.out.println(str.strip()); // Remove both leading and trailing spaces
    System.out.println(str.stripLeading()); // Remove leading spaces
    System.out.println(str.stripTrailing()); // Remove trailing spaces

9. Text Blocks (Java 13, Java 15)

  • Purpose: Defines multi-line strings using """. Text blocks offer a more concise and readable way to write multi-line strings.
  • Example:
    String textBlock = """
    {
    "name": "John",
    "age": 30
    }
    """;
    System.out.println(textBlock);
    Output:
    {
    "name": "John",
    "age": 30
    }

10. String.stripIndent() and String.transform() (Java 12)

  • Purpose:
    • stripIndent(): Removes common indentation from multi-line text.
    • transform(Function<String, R>): Used for chaining operations on strings.
  • Example:
    String result = " Hello ".transform(String::strip);
    System.out.println(result); // Output: "Hello"

11. StringTemplate (Java 21)

  • Purpose: Introduces string templates that can be defined using STR. with placeholders, providing an elegant way to perform string interpolation.
  • Example:
    String name = "Alice";
    int age = 25;
    String result = STR."Hello, \{name}. You are \{age} years old.".toString();
    System.out.println(result);
    Output:
    Hello, Alice. You are 25 years old.

Summary

Compared to Java 11, Java 21 has mainly added tools for simplifying string manipulation and text processing, such as:

  • Text blocks make writing multi-line strings simpler.
  • New methods like stripIndent(), translateEscapes(), and formatted() provide more flexible string manipulation capabilities.
  • Java 21's StringTemplate offers a modern way to perform string interpolation, making the code more concise and readable.

These improvements greatly enhance Java's capabilities in handling strings, improving developer productivity.

Gradle maven-publish Plugin

To configure the maven-publish plugin in a build.gradle.kts file using Kotlin DSL, follow these steps:

  1. Add the maven-publish plugin in the plugins block.
  2. Configure the publishing block to set up the content and the target for publishing.

Here’s a complete example:

plugins {
kotlin("jvm") version "1.9.0" // Example: Add Kotlin plugin
`maven-publish` // Add maven-publish plugin
}

group = "com.example"
version = "1.0.0"

java {
withJavadocJar()
withSourcesJar()
}

publishing {
publications {
create<MavenPublication>("mavenJava") {
from(components["java"]) // Publish Java component

// Optional: Set POM information
pom {
name.set("My Library")
description.set("A description of my library")
url.set("https://github.com/example/my-library")

licenses {
license {
name.set("The Apache License, Version 2.0")
url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
}
}
developers {
developer {
id.set("example")
name.set("Developer Name")
email.set("developer@example.com")
}
}
scm {
connection.set("scm:git:git://github.com/example/my-library.git")
developerConnection.set("scm:git:ssh://github.com:example/my-library.git")
url.set("https://github.com/example/my-library")
}
}
}
}

repositories {
maven {
val releasesRepoUrl = uri("https://your.repository.url/releases")
val snapshotsRepoUrl = uri("https://your.repository.url/snapshots")
url = if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl

credentials {
username = project.findProperty("repo.user") as String? ?: "defaultUser"
password = project.findProperty("repo.password") as String? ?: "defaultPassword"
}
}
}
}

Explanation:

  1. Plugin Configuration:

    plugins {
    `maven-publish`
    }

    Use the maven-publish plugin to publish to a Maven repository.

  2. Published Component:

    from(components["java"])

    Use from(components["java"]) to publish the Java component. For Kotlin projects, you can publish the kotlin component if needed.

  3. Generate Sources and Javadoc JAR:

    java {
    withJavadocJar()
    withSourcesJar()
    }

    This generates JAR files containing Javadoc and source code, which helps developers use the library.

  4. POM Information: You can configure project metadata in the pom block, such as descriptions, developer information, and licenses, which will be included in the generated POM.xml file.

  5. Target Repository:

    repositories {
    maven {
    val releasesRepoUrl = uri("https://your.repository.url/releases")
    val snapshotsRepoUrl = uri("https://your.repository.url/snapshots")
    url = if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl

    credentials {
    username = project.findProperty("repo.user") as String? ?: "defaultUser"
    password = project.findProperty("repo.password") as String? ?: "defaultPassword"
    }
    }
    }

    Configure the target repository in repositories and decide whether to publish to the releases or snapshots repository based on the version.

You can then run the following command to publish your library:

./gradlew publish

This is how you configure the maven-publish plugin in Kotlin DSL, and I hope it helps!

Differences between add() and addProvider() in Gradle Dependencies

Let me explain in detail the difference between the add() and addProvider() methods, and how they are used in your code.

Background

In Gradle, DependencyHandler provides a series of methods for adding dependencies to a project's configuration. Common methods include:

  • add(String configurationName, Object dependencyNotation)
  • addProvider(String configurationName, Provider<?> dependencyProvider)

Both methods are used to add dependencies to a specified configuration, but they differ in how they handle and add dependencies in terms of timing.

add() Method

The add() method is used to add dependencies to a specified configuration immediately. Its parameters are:

  • configurationName: The name of the configuration (e.g., "implementation", "testCompile").
  • dependencyNotation: The dependency notation, which can be a string (e.g., "org.apache.commons:commons-lang3:3.12.0"), a project object, a file collection, etc.

When you call add(), Gradle will immediately resolve and add the dependency.

Example:

project.getDependencies().add("implementation", "org.apache.commons:commons-lang3:3.12.0");

addProvider() Method

The addProvider() method is used to defer adding dependencies. Its parameters are:

  • configurationName: The name of the configuration.
  • dependencyProvider: A Provider object that provides the dependency notation.

Using addProvider() means that the resolution and addition of the dependency are deferred until Gradle actually needs the information. This is useful when the dependency information is not determined at configuration time or needs to be computed later.

Example:

Provider<String> dependencyNotationProvider = someExtension.getVersion().map(version -> "org.example:library:" + version);
project.getDependencies().addProvider("implementation", dependencyNotationProvider);

Application in Code

Original Code Analysis

project.getDependencies().addProvider(
jooqGeneratorRuntime.getName(),
jooqExtension.getEdition()
.map(e -> e.getGroupId() + ":jooq-codegen")
.flatMap(ga -> jooqExtension.getVersion().map(v -> ga + ":" + v))
);

In this code:

  • The dependency's construction is deferred: Both jooqExtension.getEdition() and jooqExtension.getVersion() return Provider objects, meaning these values might not be determined during configuration and will only be resolved at some point in the future (e.g., during task execution).
  • Uses the addProvider() method: Since the dependency representation is a Provider<String>, the addProvider() method is used to ensure the dependency is resolved and added when needed.

Adding the Current Project as a Dependency

project.getDependencies().add(jooqGeneratorRuntime.getName(), project);

In this case:

  • Uses add() method directly: Since the dependency to be added is the current project object, which is already known during configuration, there is no need to defer its addition.
  • Dependency is added immediately: Since the dependency is already determined, it can be added directly.

Why Differentiate Between add() and addProvider()

  1. Availability of Dependencies:

    • If all information about a dependency is known during configuration (such as a fixed string or object), you can use the add() method.
    • If the dependency information will only be determined later (e.g., depending on the output of other tasks or user configuration), the addProvider() method should be used.
  2. Benefits of Deferred Configuration:

    • Using Provider can avoid unnecessary computation during the configuration phase, improving build performance.
    • It ensures the dependency information is the latest, especially when version or other properties may change.

Summary

  • add() method: Used to add a known dependency immediately.
  • addProvider() method: Used to defer adding a dependency, where the dependency information is provided by a Provider, suitable for cases where dependency information is not determined during the configuration phase.

In your code, when adding the current project as a dependency, the dependency (project object) is known, so the add() method is used. In contrast, when adding the jOOQ code generator dependency, the dependency information needs to be fetched from jooqExtension, and this information may not be available during configuration, hence the addProvider() method is used to defer resolution and addition.

I hope this explanation helps you understand the difference between add() and addProvider() and their specific application in your code.