Skip to content
This repository has been archived by the owner on Aug 19, 2020. It is now read-only.

KT-6653: Implementing Gradle's Java Bean structured interfaces in Kotlin is incredibly verbose #1026

Open
JLLeitschuh opened this issue Aug 10, 2018 · 9 comments

Comments

@JLLeitschuh
Copy link
Contributor

JLLeitschuh commented Aug 10, 2018

As an plugin developer implementing interfaces in Kotlin, some of the bean interfaces can be incredibly verbose to implement.

For example, if, in my plugin, I want to implement Named I need to create a separate property and also override the getName method.

data class SomethingNamed(
    val aName: String,
    var someOtherData: Data
): Named {
    override fun getName(): String {
        return aName
    }
}

If the Named interface were instead something like the below:

interface Named {
    val name: String
}

The implementation of such a class would be far simpler:

data class SomethingNamed(
    override val name: String,
    var someOtherData: Data
): Named

There are various other locations throughout the Gradle API where there are Bean style interfaces that would be simpler for plugin developers to work with if they were kotlinified.

An alternative to this, is to dynamically weave in the Kotlin annotations into the bytecode so that the Kotlin compiler sees these bean interfaces as having properties. I think spring does something similar with their API's to allow for better interop with Kotlin.

This change wouldn't be a binary breaking change for any existing Java or Groovy plugins, however, making such a change after the 1.0 release of the Kotlin DSL would constitute an API breaking change for Kotlin plugins.

Longer Example

This problem gets more complicated when you start getting even bigger bean interfaces

data class CustomPasswordCredentials(
    @get:Input
    var user: String?,
    @get:Input
    var pass: String?
) : PasswordCredentials {
    override fun setUsername(userName: String?) {
        user = userName
    }

    override fun getUsername(): String? {
        return user
    }

    override fun getPassword(): String? {
        return pass
    }

    override fun setPassword(password: String?) {
        pass = password
    }
}
@eskatos
Copy link
Member

eskatos commented Aug 10, 2018

Yep, the required extra ceremony is annoying.

Given the following Java interface:

public interface InterfaceWithJavaBeanProperty {
    String getFoo();
    void setFoo(String value);
}

when implementing in Kotlin one has to write:

class JavaBeanPropertyFromJavaInterface(
    private var foo: String
) : InterfaceWithJavaBeanProperty {
    override fun getFoo(): String = foo
    override fun setFoo(value: String) { foo = value }
}

It feels like this should be enough:

class JavaBeanPropertyFromJavaInterface(override var foo: String) : InterfaceWithJavaBeanProperty

Could be seen as a Kotlin Java interop issue/feature. @JLLeitschuh could you please try find related KT issues?

@sdeleuze, any chance you could enlight us if something was done for Spring about that?

@JLLeitschuh
Copy link
Contributor Author

JLLeitschuh commented Aug 10, 2018

The TL;DR this is a really complicated issue that I don't think will ever be fixed in the Kotlin Compiler.

https://youtrack.jetbrains.com/issue/KT-6653

This is a rather deep issue, unfortunately. It's unlikely that we'll ever make it work the way you'd like
- @abreslav

@eskatos
Copy link
Member

eskatos commented Aug 10, 2018

@JLLeitschuh thanks, that's unfortunate
Could you also please edit the issue title to better reflect the issue at hand?

@eskatos
Copy link
Member

eskatos commented Aug 10, 2018

@JLLeitschuh actually, nevermind, I edited the issue title

@eskatos eskatos changed the title Convert some Gradle Java Bean interfaces to Kotlin or weave Kotlin annotations into bytecode KT-6653 - Kotlin properties do not override Java-style getters and setters Aug 10, 2018
@JLLeitschuh JLLeitschuh changed the title KT-6653 - Kotlin properties do not override Java-style getters and setters KT-6653: Implementing Gradle's Java based interfaces is Kotlin is incredibly verbose Aug 10, 2018
@JLLeitschuh JLLeitschuh changed the title KT-6653: Implementing Gradle's Java based interfaces is Kotlin is incredibly verbose KT-6653: Implementing Gradle's Java Bean structured interfaces is Kotlin is incredibly verbose Aug 10, 2018
@eskatos
Copy link
Member

eskatos commented Aug 10, 2018

@JLLeitschuh you beat me to it, good, I like your new title

@JLLeitschuh
Copy link
Contributor Author

JLLeitschuh commented Aug 10, 2018

A simple fix would be to simply convert those interfaces to Kotlin but that would mean adding the Kotlin compiler to the core Gradle build. I'm guessing that will cause a heated discussion.

@bamboo bamboo changed the title KT-6653: Implementing Gradle's Java Bean structured interfaces is Kotlin is incredibly verbose KT-6653: Implementing Gradle's Java Bean structured interfaces in Kotlin is incredibly verbose Aug 10, 2018
@eskatos
Copy link
Member

eskatos commented Aug 10, 2018

@JLLeitschuh don't forget that the Gradle Java API is consumed by codebases in Java, Groovy, Kotlin, etc...

@JLLeitschuh
Copy link
Contributor Author

Having these interfaces implemented in Kotlin I don't think would add a dependency upon the Kotlin standard lib.

I think that this compiled by the java compiler

interface Named {
    String getName();
}

And this compiled by the kotlin compiler:

interface Named {
    val name: String
}

Will produce pretty much the same bytecode. The kotlin compiler simply adds a bunch of meta annotations to the interface.

Converting these interfaces to Kotlin wouldn't impact consumers in Java or Groovy as far as I'm aware.

@JLLeitschuh
Copy link
Contributor Author

JLLeitschuh commented Oct 9, 2018

This same problem arises when a plugin author overrides a task like SourceTask and wants to make it cacheable.

For example, if you have this before:

@CacheableTask
open class KtlintCheckTask @Inject constructor(
    private val objectFactory: ObjectFactory
) : SourceTask() {
}

Interacting with this task in Kotlin looks like this:

tasks.withType<KtlintCheckTask>().configure {
    source.matching { [whatever] }
}

When you want to make the task cacheable, you have to change it to this:

@CacheableTask
open class KtlintCheckTask @Inject constructor(
    private val objectFactory: ObjectFactory
) : SourceTask() {

    @InputFiles
    @SkipWhenEmpty
    @PathSensitive(PathSensitivity.RELATIVE)
    override fun getSource(): FileTree { return super.getSource() }
}

Which becomes an API breaking change requiring API consumers to change their code to this:

tasks.withType<KtlintCheckTask>().configure {
    getSource().matching { [whatever] }
}

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

2 participants