Replace tokens in java source code

I would like to improve my current ReplaceVersionPlugin (buildSrc/src/main/kotlin/versioning.gradle.kts)

By default the plugin should replace all occurrences of @VERSION@ with project.version in my Java code.

It works, but has 1 big problem:

It injects itself as source (setSource) for every JavaCompile task, even if the task has already another input set. I’ve verified this behaviour by copying the ReplaceVersionPlugin to ReplaceVersionPlugin2 and applying it. The id(...) order becomes important and one of them will be ignored.

My questions are:

  1. How would this be implemented correctly.
  2. Is there an easier way to replace tokens in java code.
open class ReplaceVersionExtension @Inject constructor(
    project: Project
) {
    val tokens: MapProperty<String, String> = project.objects.mapProperty(String::class.java, String::class.java)
        .convention(project.provider { mutableMapOf("VERSION" to "${project.version}") })
}

val replaceVersion = extensions.create<ReplaceVersionExtension>("replaceVersion")

val replaceVersionInSource by tasks.registering(Sync::class) {
    from(project.the<SourceSetContainer>()[SourceSet.MAIN_SOURCE_SET_NAME].java)
    inputs.property("tokens", replaceVersion.tokens)

    // DeferredReplaceTokens has been added as separate code block below
    filter<DeferredReplaceTokens>("tokens" to replaceVersion.tokens)

    into(layout.buildDirectory.dir("src"))
}

// ======= THIS SHOULD PROBABLY BE MODIFIED  ======= 
tasks.withType<JavaCompile>().configureEach {
    setSource(replaceVersionInSource.get().outputs)
}

My code is based on

and

which led to:

class DeferredReplaceTokens(`in`: Reader) : FilterReader(`in`) {

    private var actualReader: FilterReader? = null

    var tokens: MapProperty<String, String>? = null

    private fun reader(): FilterReader {
        actualReader = actualReader ?: ReplaceTokens(this.`in`).also { replaceTokens ->
            val tokens = this.tokens
            if (tokens == null) {
                println("No tokens set.  tokens is null")
            } else {
                tokens.keySet().get()
                    .map { key -> key to tokens.getting(key).get() }
                    .onEach { println("Token: ${it.first} : ${it.second}") }
                    .map { (key, value) -> ReplaceTokens.Token().also { it.key = key; it.value = value } }
                    .forEach(replaceTokens::addConfiguredToken)
            }
        }
        return actualReader!!
    }


    override fun read(cbuf: CharArray, off: Int, len: Int): Int = reader().read(cbuf, off, len)
    override fun read(): Int = reader().read()
    override fun close() = reader().close()
}

I would not replace tokens in Java code at all.
I would have a properties file with placeholders.
The processResources task is prepared to do some filtering or placeholder replacement.
The code would then read the value from the properties file, or from one class that reads the properties file.

I have already started replacing some version tokens with String implementationVersion = MyApplication.class.getPackage().getImplementationVersion();.
It will however take some time until I manage to clean up everything.

The current version is perfectly fine for us, as I know that no other plugin changes the source of the JavaCompile task.

I am currently trying to really understand gradle and was hoping that the answer to my questions would help me.

1 Like

It is not uncommon for Java developers to need to replace tokens in their source code. This can be done for a variety of reasons, such as wanting to change the value of a constant or needing to update a URL. Whatever the reason, it is relatively easy to do.

There are a few different ways to replace tokens in Java source code. One way is to use a text editor that has search and replace functionality. This can be done with a tool like Notepad++ or Vim. Another way is to use a command line tool like sed or awk. And finally, there are some IDEs that have token replacement functionality built in, like Eclipse or IntelliJ IDEA.

Which method you use will likely depend on your personal preferences and the specific situation you are in.

The question is not about a manual one-time replacement that could be done using a tool like you describe.
The question is about an automatic replacement during building the software, e. g. to insert the current version or timestamp or Git commit or whatever.
And for that it is much better and easier to do the replacement in some resource file like a properties file or to add it to the manifest and in the code just read it from there.
I usually prefer a dedicated properties file though, due to the horrible bad-practice of builing repackaged fat jars that breaks the manifest approach.