Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

jte-models: Add support to generate Kotlin code #282

Merged
merged 5 commits into from
Oct 6, 2023

Conversation

marcospereira
Copy link
Contributor

@marcospereira marcospereira commented Oct 5, 2023

What?

This adds support to generate Kotlin code (instead of Java) when using the jte-models extension. There are a few inconveniences when mixing both languages (clashing names such as List, which are different for both, not having to import standard-lib classes in Kotlin that would break the Java generate code), and having this will solve that.

How

Simply by providing a language configuration to the extension. The default is Java, so people upgrading won't have to change anything in their build files. An example:

jte {
    generate()
    binaryStaticContent = true
    jteExtension('gg.jte.models.generator.ModelExtension') {
        language = 'kotlin'
    }
}

Generate code

The generated code looks like this:

@file:Suppress("ktlint")
package test.mytemplates

import gg.jte.models.runtime.*

interface Templates {
    
    @JteView("hello.jte")
    fun hello(message: java.lang.String): JteModel

}
@file:Suppress("ktlint")
package test.mytemplates

import gg.jte.TemplateEngine
import gg.jte.models.runtime.*

class DynamicTemplates(private val engine: TemplateEngine) : Templates {
    
    override fun hello(message: java.lang.String): JteModel {
        val paramMap = mapOf(
        
            "message" to message,
        )
        return DynamicJteModel(engine, "hello.jte", paramMap);
    }

}
@file:Suppress("ktlint")
package test.mytemplates

import gg.jte.models.runtime.*
import gg.jte.ContentType
import gg.jte.TemplateOutput
import gg.jte.html.HtmlInterceptor
import gg.jte.html.HtmlTemplateOutput

class StaticTemplates : Templates {
    
    override fun hello(message: java.lang.String): JteModel {
        return StaticJteModel<TemplateOutput>(
            ContentType.Plain,
            { output, interceptor -> test.mytemplates.JtehelloGenerated.render(output, interceptor, message) },
            "hello.jte",
            "test.mytemplates",
            test.mytemplates.JtehelloGenerated.JTE_LINE_INFO
        );
    }

}

Copy link
Contributor Author

@marcospereira marcospereira left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few comments and questions.

try {
language = Language.valueOf(configuredLanguage);
} catch (IllegalArgumentException ex) {
// how to report wrong conciguration? fail or default to Java?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@casid, I'm unsure how (or even if) to do this. It is an open question for this PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion you should fail the build in this case. Building Java when a different language was specified is not what the user wants.

I think throwing an exception with a good description would be good enough, but you should test that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 440c1f0.

@@ -21,7 +21,7 @@ public class ModelExtension implements JteExtension {

@Override
public String name() {
return "Generate type-safe model facade for templates";
return "Generate type-safe model facade for templates in Java";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also unsure about the impact of changing this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name isn't actually used anywhere at the moment.

@@ -23,17 +23,27 @@ public class ModelGenerator {
private final String targetClassName;
private final String interfaceName;

private final Language language;

public ModelGenerator(TemplateEngine engine, String templateSubDirectory, String targetClassName, String interfaceName) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept this constructor for compatibility reasons. But it may be unnecessary now since it is not used in the code base.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ModelGenerator is internal implementation, so it is not necessary to keep the constructor overload.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in aac2a43.

@edward3h
Copy link
Contributor

edward3h commented Oct 5, 2023

Looks good to me. Just one question: since Kotlin can interoperate with Java classes, what is the benefit of generating Kotlin versions of the classes? (I'm not much of a Kotlin user)

@marcospereira
Copy link
Contributor Author

Looks good to me. Just one question: since Kotlin can interoperate with Java classes, what is the benefit of generating Kotlin versions of the classes? (I'm not much of a Kotlin user)

Interoperability is quite good, but there are places where it fails. For example, if you have a Kte template such as:

@param messages: List<String>

<html lang="pt">
    <head>
        <title>First Page</title>
    </head>
    <body>
        <h1>message</h1>
    </body>
</html>

List is a kotlin.collections.List, and users don't need to import manually. The generated (Java) interface misses the import and is then broken:

package br.ufpe.liber;
import gg.jte.models.runtime.*;

public interface Templates {
    
    @JteView("index.kte")
    JteModel index(List<String> messages);

}

Compilation error:

Templates.java:8: error: cannot find symbol
    JteModel index(List<String> messages);
                   ^
  symbol:   class List
  location: interface Templates

There are workarounds, but then the user needs to use them constantly, which impacts the developer experience.

@marcospereira
Copy link
Contributor Author

Thanks for reviewing, @edward3h. I pushed a couple of commits after your feedback. :-)

@edward3h
Copy link
Contributor

edward3h commented Oct 6, 2023

Looks good. Thank you for your contribution.

@codecov
Copy link

codecov bot commented Oct 6, 2023

Codecov Report

Attention: 1 lines in your changes are missing coverage. Please review.

Comparison is base (9230876) 91.09% compared to head (1888f90) 91.13%.

Additional details and impacted files
@@             Coverage Diff              @@
##               main     #282      +/-   ##
============================================
+ Coverage     91.09%   91.13%   +0.03%     
- Complexity     1181     1186       +5     
============================================
  Files            75       76       +1     
  Lines          3100     3113      +13     
  Branches        479      480       +1     
============================================
+ Hits           2824     2837      +13     
+ Misses          169      168       -1     
- Partials        107      108       +1     
Files Coverage Δ
...rc/main/java/gg/jte/models/generator/Language.java 100.00% <100.00%> (ø)
...main/java/gg/jte/models/generator/ModelConfig.java 78.94% <100.00%> (+9.71%) ⬆️
...n/java/gg/jte/models/generator/ModelGenerator.java 93.10% <100.00%> (+0.79%) ⬆️
...ls/src/main/java/gg/jte/models/generator/Util.java 61.90% <100.00%> (+1.90%) ⬆️
...n/java/gg/jte/models/generator/ModelExtension.java 81.25% <50.00%> (+14.58%) ⬆️

... and 1 file with indirect coverage changes

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@casid
Copy link
Owner

casid commented Oct 6, 2023

Thanks for the contribution @marcospereira and thanks for the review @edward3h.

I know it is against the Java conventions, but so far enum constants in the jte project have been camel case. Could we rename the Language enum values to Java and Kotlin?

I think it is also okay to be case sensitive here when we parse the setting and do a Language.valueOf(configuredLanguage); as you had it initially. This will always be an error message at build time, never at runtime. We probably do not want to support users entering jAvA and the like :-)

Other than that, this looks fantastic!

@marcospereira
Copy link
Contributor Author

Thanks, @casid.

I know it is against the Java conventions, but so far enum constants in the jte project have been camel case. Could we rename the Language enum values to Java and Kotlin?

Done in a059cd3.

I think it is also okay to be case sensitive here when we parse the setting and do a Language.valueOf(configuredLanguage); as you had it initially. This will always be an error message at build time, never at runtime. We probably do not want to support users entering jAvA and the like :-)

Done in 1888f90.

This should be good to go now. :-)

@casid
Copy link
Owner

casid commented Oct 6, 2023

Wow, that was quick! I'll merge this as soon as the CI build is done.

@marcospereira
Copy link
Contributor Author

Wow, that was quick! I'll merge this as soon as the CI build is done.

Thanks! Also, when can we have a release with those changes?

@casid casid merged commit 64786c0 into casid:main Oct 6, 2023
@casid
Copy link
Owner

casid commented Oct 6, 2023

There haven't been any changes since the last release, so there's nothing blocking us from releasing this asap. Will have a look at it this weekend.

@marcospereira
Copy link
Contributor Author

There haven't been any changes since the last release, so there's nothing blocking us from releasing this asap. Will have a look at it this weekend.

Thank you. No rush here, though. :-)

By the way, I just submitted a follow-up pr to update the docs. See #283. :-)

@casid
Copy link
Owner

casid commented Oct 7, 2023

@marcospereira I published version 3.1.2 which contains this feature!

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

Successfully merging this pull request may close these issues.

3 participants