Skip to content

Commit

Permalink
Create Code Generator for Kotlin and Java
Browse files Browse the repository at this point in the history
  • Loading branch information
Simon Scholz committed Nov 21, 2023
1 parent 0643597 commit d9d454f
Show file tree
Hide file tree
Showing 10 changed files with 467 additions and 159 deletions.
6 changes: 3 additions & 3 deletions java-sample/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ repositories {
}

dependencies {
implementation("io.github.simonscholz:qr-code-with-logo:0.1.0")
implementation("io.github.simonscholz:qr-code-with-logo:0.2.0-SNAPSHOT")

testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.0")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.0")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.1")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.1")
}

tasks.test {
Expand Down
12 changes: 4 additions & 8 deletions qr-code-app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ plugins {

id("io.gitlab.arturbosch.detekt")
id("org.jlleitschuh.gradle.ktlint")
id("com.github.ben-manes.versions")

id("org.graalvm.buildtools.native") version "0.9.28"
id("org.beryx.runtime") version "1.12.7"
Expand All @@ -19,6 +20,8 @@ distributions {

runtime {
addOptions("--strip-debug", "--compress", "2", "--no-header-files", "--no-man-pages")
imageDir = file("${layout.buildDirectory.asFile.get().name}/qr-code-with-logo-app")
imageZip = file("${layout.buildDirectory.asFile.get().name}/qr-code-with-logo-app.zip")

launcher {
noConsole = true
Expand All @@ -30,14 +33,6 @@ runtime {
}
}

tasks.register<Zip>("jpackageImageZip") {
dependsOn("jpackageImage")
archiveFileName = "qr-code-app.zip"
destinationDirectory = layout.buildDirectory.dir("dist")

from(layout.buildDirectory.dir("jpackage/qr-code-app/"))
}

repositories {
mavenCentral()
}
Expand All @@ -51,6 +46,7 @@ dependencies {
}
implementation("com.github.lgooddatepicker:LGoodDatePicker:11.2.1")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.16.0")
implementation("com.squareup:kotlinpoet-javapoet:1.15.1")

// implementation("io.quarkus:quarkus-awt-deployment:3.5.0")

Expand Down
24 changes: 13 additions & 11 deletions qr-code-app/src/main/kotlin/io/github/simonscholz/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package io.github.simonscholz

import io.github.simonscholz.model.QrCodeConfigViewModel
import io.github.simonscholz.observables.SwingRealm
import io.github.simonscholz.service.CodeGeneratorService
import io.github.simonscholz.service.ConfigService
import io.github.simonscholz.service.ImageService
import io.github.simonscholz.ui.FileUI
import io.github.simonscholz.ui.ImageUI
import io.github.simonscholz.ui.MainMenu
import io.github.simonscholz.ui.MainUI
Expand All @@ -13,17 +15,16 @@ import org.eclipse.core.databinding.observable.value.IObservableValue
import java.awt.event.WindowAdapter
import java.awt.event.WindowEvent
import java.awt.image.BufferedImage
import java.io.File
import javax.swing.JFrame
import javax.swing.JPanel
import javax.swing.SwingUtilities

fun main() {
// GraalVM Fix
if (System.getProperty("java.home") == null) {
println("No Java Home set, assuming that we are running from GraalVM. Fixing...")
System.setProperty("java.home", File(".").absolutePath)
}
// if (System.getProperty("java.home") == null) {
// println("No Java Home set, assuming that we are running from GraalVM. Fixing...")
// System.setProperty("java.home", File(".").absolutePath)
// }

SwingUtilities.invokeLater {
val frame = JFrame("QR Code AWT/Swing UI")
Expand All @@ -42,10 +43,11 @@ fun main() {

var alreadyAppliedOnce = false
val alreadyAppliedOnceDelegate = { alreadyAppliedOnce }
val imageService = ImageService(qrCodeConfigViewModel, alreadyAppliedOnceDelegate)
MainMenu.createFrameMenu(frame, qrCodeConfigViewModel.qrCodeContent, imageService)
val imageService = ImageService(qrCodeConfigViewModel)
val fileUi = FileUI(CodeGeneratorService(qrCodeConfigViewModel), configService, imageService, alreadyAppliedOnceDelegate)
MainMenu.createFrameMenu(frame, qrCodeConfigViewModel.qrCodeContent, fileUi, configService)

val (imagePanel, setImage) = ImageUI.createImagePanel(imageService)
val (imagePanel, setImage) = ImageUI.createImagePanel(imageService, fileUi)
val (propertiesPanel, applyOnChange) = PropertiesUI.createPropertiesUI(qrCodeConfigViewModel, dataBindingContext) {
onPropertyApply(qrCodeConfigViewModel.qrCodeContent, imageService, setImage, imagePanel)
alreadyAppliedOnce = true
Expand All @@ -54,9 +56,6 @@ fun main() {
val mainPanel = MainUI.createMainPanel(imagePanel, propertiesPanel)
frame.add(mainPanel)

frame.pack()
frame.isVisible = true

dataBindingContext.bindings.forEach {
it.model.addChangeListener {
if (applyOnChange()) {
Expand All @@ -66,6 +65,9 @@ fun main() {
}
}
configService.loadConfig()

frame.pack()
frame.isVisible = true
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
package io.github.simonscholz.service

import com.squareup.javapoet.JavaFile
import com.squareup.javapoet.MethodSpec
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.TypeSpec
import io.github.simonscholz.model.QrCodeConfigViewModel
import io.github.simonscholz.qrcode.QrPositionalSquaresConfig
import java.awt.Color
import java.awt.Image
import java.awt.image.BufferedImage
import java.io.IOException
import javax.imageio.ImageIO
import javax.lang.model.element.Modifier
import com.squareup.javapoet.ClassName as JavaClassName
import com.squareup.javapoet.TypeSpec as JavaTypeSpec

class CodeGeneratorService(private val qrCodeConfigViewModel: QrCodeConfigViewModel) {

fun generateKotlinCode(): String {
val file = FileSpec.builder("io.github.simonscholz", "QrCodeGenerator")

file.addImport("io.github.simonscholz.qrcode", "LogoShape", "QrCodeConfig", "QrCodeFactory", "QrPositionalSquaresConfig")
file.addImport("java.awt", "Color", "image.BufferedImage")
file.addImport("java.io", "File")
file.addImport("javax.imageio", "ImageIO")

file.addFunction(
FunSpec.builder("toFile")
.receiver(ClassName("java.awt.image", "BufferedImage"))
.addParameter("file", ClassName("java.io", "File"))
.addStatement("ImageIO.write(this, \"png\", file)")
.build(),
)

file.addFunction(
FunSpec.builder("main")
.addStatement(
"""
|val logo = ImageIO.read(
| QrCodeGenerator::class.java
| .classLoader
| .getResource("avatar-60x.png")
|)
""".trimMargin(),
)
.addStatement("val qrCodeGenerator = %T()", ClassName("io.github.simonscholz", "QrCodeGenerator"))
.addStatement("%N.generateQrCode(logo).toFile(File(\"qr-code.png\"))", "qrCodeGenerator")
.build(),
)

val generateQrCodeFunction = FunSpec.builder("generateQrCode")
.addParameter("logo", ClassName("java.awt", "Image"))
.addStatement(
"""
|val qrPositionalSquaresConfig = %T.Builder()
| .circleShaped(${qrCodeConfigViewModel.positionalSquareIsCircleShaped.value})
| .relativeSquareBorderRound(${qrCodeConfigViewModel.positionalSquareRelativeBorderRound.value})
| .centerColor(${colorInstanceStringKotlin(qrCodeConfigViewModel.positionalSquareCenterColor.value)})
| .innerSquareColor(${colorInstanceStringKotlin(qrCodeConfigViewModel.positionalSquareInnerSquareColor.value)})
| .outerSquareColor(${colorInstanceStringKotlin(qrCodeConfigViewModel.positionalSquareOuterSquareColor.value)})
| .outerBorderColor(${colorInstanceStringKotlin(qrCodeConfigViewModel.positionalSquareOuterBorderColor.value)})
| .build()
""".trimMargin(),
ClassName("io.github.simonscholz.qrcode", "QrPositionalSquaresConfig"),
ClassName("java.awt", "Color"),
ClassName("java.awt", "Color"),
ClassName("java.awt", "Color"),
ClassName("java.awt", "Color"),
)
.addStatement(
"""
|val qrCodeConfig = %T.Builder("${qrCodeConfigViewModel.qrCodeContent.value}")
| .qrCodeSize(${qrCodeConfigViewModel.size.value})
| .qrCodeColorConfig(
| bgColor = ${colorInstanceStringKotlin(qrCodeConfigViewModel.backgroundColor.value)},
| fillColor = ${colorInstanceStringKotlin(qrCodeConfigViewModel.foregroundColor.value)},
| )
| .qrLogoConfig(
| logo = logo,
| relativeSize = ${qrCodeConfigViewModel.logoRelativeSize.value},
| bgColor = ${colorInstanceStringKotlin(qrCodeConfigViewModel.logoBackgroundColor.value)},
| shape = %T.${qrCodeConfigViewModel.logoShape.value},
| )
| .qrBorderConfig(
| color = ${colorInstanceStringKotlin(qrCodeConfigViewModel.borderColor.value)},
| relativeSize = ${qrCodeConfigViewModel.relativeBorderSize.value},
| relativeBorderRound = ${qrCodeConfigViewModel.borderRadius.value},
| )
| .qrPositionalSquaresConfig(qrPositionalSquaresConfig)
| .build()
""".trimMargin(),
ClassName("io.github.simonscholz.qrcode", "QrCodeConfig"),
ClassName("java.awt", "Color"),
ClassName("java.awt", "Color"),
ClassName("java.awt", "Color"),
ClassName("io.github.simonscholz.qrcode", "LogoShape"),
ClassName("java.awt", "Color"),
)
.addStatement(
"""
|return %T.createQrCodeApi().createQrCodeImage(qrCodeConfig)
""".trimMargin(),
ClassName("io.github.simonscholz.qrcode", "QrCodeFactory"),
)
.returns(BufferedImage::class)
.build()

file.addType(
TypeSpec.classBuilder("QrCodeGenerator")
.addFunction(generateQrCodeFunction)
.build(),
)

return StringBuilder().apply {
file.build().writeTo(this)
}.toString()
}

fun generateJavaCode(): String {
val qrCodeGenerator = JavaTypeSpec.classBuilder("QrCodeGenerator")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)

MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addParameter(Array<String>::class.java, "args", Modifier.FINAL)
.addException(IOException::class.java)
.addStatement("final var qrCodeGenerator = new QrCodeGenerator()")
.addStatement(
"""
|final var qrCodeImage = qrCodeGenerator.generateQrCode(
| $T.read(QrCodeGenerator.class.getClassLoader()
| .getResource("your-logo.png"))
|)
""".trimMargin(),
ImageIO::class.java,
)
.addStatement(
"ImageIO.write(qrCodeImage, \"png\", new $T(\"qr-code.png\"))",
JavaClassName.get("java.io", "File"),
)
.build()
.let(qrCodeGenerator::addMethod)

val generateQrCodeMethod = MethodSpec.methodBuilder("generateQrCode")
.addModifiers(Modifier.PUBLIC)
.addException(IOException::class.java)
.addParameter(Image::class.java, "logo")
.addStatement(
"""
|final var qrPositionalSquaresConfig = new $T.Builder()
| .circleShaped(${qrCodeConfigViewModel.positionalSquareIsCircleShaped.value})
| .relativeSquareBorderRound(${qrCodeConfigViewModel.positionalSquareRelativeBorderRound.value})
| .centerColor(${colorInstanceStringJava(qrCodeConfigViewModel.positionalSquareCenterColor.value)})
| .innerSquareColor(${colorInstanceStringJava(qrCodeConfigViewModel.positionalSquareInnerSquareColor.value)})
| .outerSquareColor(${colorInstanceStringJava(qrCodeConfigViewModel.positionalSquareOuterSquareColor.value)})
| .outerBorderColor(${colorInstanceStringJava(qrCodeConfigViewModel.positionalSquareOuterBorderColor.value)})
| .build()
""".trimMargin(),
QrPositionalSquaresConfig::class.java,
Color::class.java,
Color::class.java,
Color::class.java,
Color::class.java,
)
.addStatement(
"""
|final var qrCodeConfig = new $T.Builder("${qrCodeConfigViewModel.qrCodeContent.value}}")
| .qrCodeSize(${qrCodeConfigViewModel.size.value})
| .qrCodeColorConfig(${colorInstanceStringJava(qrCodeConfigViewModel.backgroundColor.value)}, ${colorInstanceStringJava(qrCodeConfigViewModel.foregroundColor.value)})
| .qrLogoConfig(logo, ${qrCodeConfigViewModel.logoRelativeSize.value}, ${colorInstanceStringJava(qrCodeConfigViewModel.logoBackgroundColor.value)}, $T.${qrCodeConfigViewModel.logoShape.value})
| .qrBorderConfig(${colorInstanceStringJava(qrCodeConfigViewModel.borderColor.value)}, ${qrCodeConfigViewModel.relativeBorderSize.value}, ${qrCodeConfigViewModel.borderRadius.value})
| .qrPositionalSquaresConfig(qrPositionalSquaresConfig)
| .build()
""".trimMargin(),
JavaClassName.get("io.github.simonscholz.qrcode", "QrCodeConfig"),
JavaClassName.get("java.awt", "Color"),
JavaClassName.get("java.awt", "Color"),
JavaClassName.get("java.awt", "Color"),
JavaClassName.get("io.github.simonscholz.qrcode", "LogoShape"),
JavaClassName.get("java.awt", "Color"),
)
.addStatement(
"return $T.createQrCodeApi().createQrCodeImage(qrCodeConfig)",
JavaClassName.get("io.github.simonscholz.qrcode", "QrCodeFactory"),
)
.returns(BufferedImage::class.java)
.build()

qrCodeGenerator.addMethod(generateQrCodeMethod)

return StringBuilder().apply {
JavaFile.builder("io.github.simonscholz", qrCodeGenerator.build()).build().writeTo(this)
}.toString()
}

private fun colorInstanceStringKotlin(color: Color): String {
return "%T(${color.red}, ${color.green}, ${color.blue})"
}

private fun colorInstanceStringJava(color: Color): String {
return "new $T(${color.red}, ${color.green}, ${color.blue})"
}

companion object {
// https://github.com/square/javapoet/issues/831#issuecomment-817238209
private const val T = "\$T"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,39 @@ import io.github.simonscholz.model.Mapper
import io.github.simonscholz.model.QrCodeConfig
import io.github.simonscholz.model.QrCodeConfigViewModel
import java.io.File
import java.nio.file.Files
import java.nio.file.Paths
import java.util.prefs.Preferences

class ConfigService(
private val qrCodeConfigViewModel: QrCodeConfigViewModel,
) {

private val objectMapper = ObjectMapper().registerKotlinModule()
private val preferences = Preferences.userRoot().node("qr-code-app")

fun saveConfig(fileName: String = "config.json") {
val path = Paths.get(System.getProperty("user.home"), ".qr-code-app")
Files.createDirectories(path)
val qrCodeDir = path.toAbsolutePath().toString()
fun saveConfig() {
val config = Mapper.fromViewModel(qrCodeConfigViewModel)
val configJson = objectMapper.writeValueAsString(config)
preferences.put("config", configJson)
}

fun saveConfigFile(filePath: String) {
val config = Mapper.fromViewModel(qrCodeConfigViewModel)
val configJson = objectMapper.writeValueAsString(config)
val configJsonFile = File(qrCodeDir, fileName)
val finalFilePath = if (filePath.endsWith(".json")) filePath else "$filePath.json"
val configJsonFile = File(finalFilePath)
configJsonFile.writeText(configJson)
}

fun loadConfig() {
val path = Paths.get(System.getProperty("user.home"), ".qr-code-app")
val configJsonFile = File(path.toAbsolutePath().toString(), "config.json")
if (configJsonFile.exists()) {
val configJson = configJsonFile.readText()
val config = objectMapper.readValue(configJson, QrCodeConfig::class.java)
preferences.get("config", null)?.let {
val config = objectMapper.readValue(it, QrCodeConfig::class.java)
Mapper.applyViewModel(config, qrCodeConfigViewModel)
}
}

fun loadConfigFile(filePath: String) {
val configJsonFile = File(filePath)
val configJson = configJsonFile.readText()
val config = objectMapper.readValue(configJson, QrCodeConfig::class.java)
Mapper.applyViewModel(config, qrCodeConfigViewModel)
}
}
Loading

0 comments on commit d9d454f

Please sign in to comment.