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

Add three extensions for JsonToKotlinClass plugin #349

Merged
merged 4 commits into from
May 22, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/main/kotlin/extensions/ExtensionsCollector.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import extensions.ted.zeng.PropertyAnnotationLineSupport
import extensions.wu.seal.*
import extensions.xu.rui.PrimitiveTypeNonNullableSupport
import extensions.nstd.ReplaceConstructorParametersByMemberVariablesSupport
import extensions.yuan.varenyzc.BuildFromJsonObjectSupport
import wu.seal.jsontokotlin.interceptor.AnalyticsSwitchSupport
import extensions.yuan.varenyzc.CamelCaseSupport
import extensions.yuan.varenyzc.NeedNonNullableClassesSupport

/**
* extension collect, all extensions will be hold by this class's extensions property
Expand All @@ -28,6 +31,9 @@ object ExtensionsCollector {
ForceInitDefaultValueWithOriginJsonValueSupport,
DisableDataClassSupport,
ReplaceConstructorParametersByMemberVariablesSupport,
AnalyticsSwitchSupport
AnalyticsSwitchSupport,
CamelCaseSupport,
BuildFromJsonObjectSupport,
NeedNonNullableClassesSupport
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package extensions.yuan.varenyzc

import extensions.Extension
import wu.seal.jsontokotlin.model.builder.CodeBuilderConfig
import wu.seal.jsontokotlin.model.builder.KotlinCodeBuilder
import wu.seal.jsontokotlin.model.classscodestruct.KotlinClass
import wu.seal.jsontokotlin.ui.jCheckBox
import wu.seal.jsontokotlin.ui.jHorizontalLinearLayout
import javax.swing.JPanel

object BuildFromJsonObjectSupport : Extension() {

const val configKey = "top.varenyzc.build_from_json_enable"

override fun createUI(): JPanel {
return jHorizontalLinearLayout {
jCheckBox(
"Enable build from JsonObject",
varenyzc marked this conversation as resolved.
Show resolved Hide resolved
getConfig(configKey).toBoolean(),
{ isSelected -> setConfig(configKey, isSelected.toString()) }
)
fillSpace()
}
}

override fun intercept(kotlinClass: KotlinClass): KotlinClass {
CodeBuilderConfig.instance.setConfig(
KotlinCodeBuilder.CONF_BUILD_FROM_JSON_OBJECT,
getConfig(configKey).toBoolean()
)
return kotlinClass
}

override fun intercept(originClassImportDeclaration: String): String {

val classImportClassString = "import org.json.JSONObject"
return if (getConfig(configKey).toBoolean()) {
originClassImportDeclaration.append(classImportClassString).append("\n")
} else {
originClassImportDeclaration
}
}
}
60 changes: 60 additions & 0 deletions src/main/kotlin/extensions/yuan/varenyzc/CamelCaseSupport.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package extensions.yuan.varenyzc

import extensions.Extension
import wu.seal.jsontokotlin.model.classscodestruct.DataClass
import wu.seal.jsontokotlin.model.classscodestruct.KotlinClass
import wu.seal.jsontokotlin.ui.jCheckBox
import wu.seal.jsontokotlin.ui.jHorizontalLinearLayout
import wu.seal.jsontokotlin.utils.LogUtil
import java.lang.StringBuilder
import javax.swing.JPanel

object CamelCaseSupport : Extension() {

/**
* Config key can't be private, as it will be accessed from `library` module
*/
@Suppress("MemberVisibilityCanBePrivate")
const val configKey = "top.varenyzc.camel_case_enable"

override fun createUI(): JPanel {
return jHorizontalLinearLayout {
jCheckBox(
"Enable Camel-Case",
varenyzc marked this conversation as resolved.
Show resolved Hide resolved
getConfig(configKey).toBoolean(),
{ isSelected -> setConfig(configKey, isSelected.toString()) })
fillSpace()
}
}

override fun intercept(kotlinClass: KotlinClass): KotlinClass {
if (kotlinClass is DataClass) {
return if (getConfig(configKey).toBoolean()) {
val originProperties = kotlinClass.properties
val newProperties = originProperties.map {
val oldName = it.name
if (oldName.isNotEmpty() && oldName.contains("_")) {
val newName = StringBuilder().run {
val list = oldName.split("_")
for (s in list) {
if (this.isEmpty()) {
append(s)
} else {
append(s.substring(0, 1).toUpperCase())
append(s.substring(1).toLowerCase())
}
}
toString()
}
it.copy(name = newName)
} else it
}
kotlinClass.copy(properties = newProperties)
} else {
kotlinClass
}
} else {
return kotlinClass
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package extensions.yuan.varenyzc

import com.intellij.util.ui.JBDimension
import extensions.Extension
import wu.seal.jsontokotlin.model.classscodestruct.DataClass
import wu.seal.jsontokotlin.model.classscodestruct.KotlinClass
import wu.seal.jsontokotlin.ui.*
import javax.swing.JPanel

object NeedNonNullableClassesSupport : Extension() {

const val prefixKeyEnable = "top.varenyzc.need_nonnullable_classes_enable"
const val prefixKey = "top.varenyzc.need_nonnullable_classes"

override fun createUI(): JPanel {

val prefixJField = jTextInput(getConfig(prefixKey), getConfig(prefixKeyEnable).toBoolean()) {
addFocusLostListener {
if (getConfig(prefixKeyEnable).toBoolean()) {
setConfig(prefixKey, text)
}
}
maximumSize = JBDimension(400, 30)
}

return jHorizontalLinearLayout {
jCheckBox("Need NonNullable classes: ", getConfig(prefixKeyEnable).toBoolean(), { isSelected ->
varenyzc marked this conversation as resolved.
Show resolved Hide resolved
setConfig(prefixKeyEnable, isSelected.toString())
prefixJField.isEnabled = isSelected
})
add(prefixJField)
}
}

override fun intercept(kotlinClass: KotlinClass): KotlinClass {
return if (kotlinClass is DataClass) {
if (getConfig(prefixKeyEnable).toBoolean()) {
val list = getConfig(prefixKey).split(',')
val originProperties = kotlinClass.properties
val newProperties = originProperties.map {
val oldType = it.type
if (oldType.isNotEmpty()) {
val newType = if (oldType.contains("List<")) {
val innerType = oldType.substring(oldType.indexOf('<') + 1, oldType.indexOf('>'))
when {
list.contains(innerType) -> {
"List<$innerType>?"
}
list.contains("List") -> {
"List<$innerType?>"
}
else -> {
"List<$innerType?>?"
}
}
} else {
if (list.contains(oldType.replace("?", ""))) {
oldType.replace("?","")
} else {
"${oldType}?"
}
}
it.copy(type = newType)
} else {
it
}
}
kotlinClass.copy(properties = newProperties)
} else {
kotlinClass
}
} else {
kotlinClass
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ package wu.seal.jsontokotlin.model.builder

import wu.seal.jsontokotlin.model.classscodestruct.*
import wu.seal.jsontokotlin.model.classscodestruct.Annotation
import wu.seal.jsontokotlin.utils.addIndent
import wu.seal.jsontokotlin.utils.getCommentCode
import wu.seal.jsontokotlin.utils.getIndent
import wu.seal.jsontokotlin.utils.toAnnotationComments
import wu.seal.jsontokotlin.utils.*

/**
* kotlin class code generator
Expand Down Expand Up @@ -49,10 +46,13 @@ data class KotlinCodeBuilder(
companion object {
const val CONF_KOTLIN_IS_DATA_CLASS = "code.builder.kotlin.isDataClass"
const val CONF_KOTLIN_IS_USE_CONSTRUCTOR_PARAMETER = "code.builder.kotlin.isUseConstructorParameter"
const val CONF_BUILD_FROM_JSON_OBJECT = "code.builder.buildFromJsonObject"
}

private var isDataClass: Boolean = true
private var isUseConstructorParameter: Boolean = true
private var isBuildFromJsonObject: Boolean = false
private val baseTypeList = listOf<String>("Int", "String", "Boolean", "Double", "Float", "Long")

val referencedClasses: List<KotlinClass>
get() {
Expand All @@ -71,6 +71,7 @@ data class KotlinCodeBuilder(
override fun getCode(): String {
isDataClass = getConfig(CONF_KOTLIN_IS_DATA_CLASS, isDataClass)
isUseConstructorParameter = getConfig(CONF_KOTLIN_IS_USE_CONSTRUCTOR_PARAMETER, isUseConstructorParameter)
isBuildFromJsonObject = getConfig(CONF_BUILD_FROM_JSON_OBJECT, isBuildFromJsonObject)

if (fromJsonSchema && properties.isEmpty()) return ""
return buildString {
Expand Down Expand Up @@ -123,10 +124,12 @@ data class KotlinCodeBuilder(
private fun genBody(sb: StringBuilder) {
val nestedClasses = referencedClasses.filter { it.modifiable }
val hasMember = !isUseConstructorParameter && properties.isNotEmpty()
if (!hasMember && nestedClasses.isEmpty()) return
val isBuildFromJson = isBuildFromJsonObject && properties.isNotEmpty()
if (!hasMember && !isBuildFromJson && nestedClasses.isEmpty()) return
val indent = getIndent()
val bodyBuilder = StringBuilder()
if (hasMember) genJsonFields(bodyBuilder, indent, false)
if (isBuildFromJson) genBuildFromJsonObject(bodyBuilder, indent)
if (nestedClasses.isNotEmpty()) {
val nestedClassesCode = nestedClasses.map { it.getCode() }.filter { it.isNotBlank() }.joinToString("\n\n")
bodyBuilder.append(nestedClassesCode.addIndent(indent))
Expand All @@ -153,4 +156,42 @@ data class KotlinCodeBuilder(
}
}

}
private fun genBuildFromJsonObject(sb: java.lang.StringBuilder, indent: String) {
sb.newLine()
sb.append(indent * 1).append("companion object {").newLine()
sb.append(indent * 2).append("@JvmStatic").newLine()
sb.append(indent * 2).append("fun buildFromJson(jsonObject: JSONObject?): $name? {").newLine().newLine()
sb.append(indent * 3).append("jsonObject?.run {").newLine()
sb.append(indent * 4).append("return $name(").newLine()
properties.filterNot { excludedProperties.contains(it.name) }.forEachIndexed { index, property ->
when {
baseTypeList.contains(property.type.replace("?","")) -> {
sb.append(indent * 5).append("opt${property.type.replace("?", "")}(\"${property.originName}\")")
}
property.type.contains("List<") -> {
val type = property.type.substring(property.type.indexOf('<') + 1, property.type.indexOf('>'))
sb.append(indent * 5).append("Array${property.type.replace("?", "")}().apply {").newLine()
sb.append(indent * 6).append("optJSONArray(\"${property.originName}\")?.let {").newLine()
sb.append(indent * 7).append("for(i in 0 until it.length()) {").newLine()
sb.append(indent * 8).append("this.add($type.buildFromJson(it.getJSONObject(i)))").newLine()
sb.append(indent * 7).append("}").newLine()
sb.append(indent * 6).append("}").newLine()
sb.append(indent * 5).append("}")
}
else -> {
sb.append(indent * 5).append("${property.type.replace("?", "")}.buildFromJson(optJSONObject(\"${property.originName}\"))")
}
}

if (index < properties.size - 1) {
sb.append(",")
}
sb.newLine()
}
sb.append(indent * 4).append(")").newLine()
sb.append(indent * 3).append("}").newLine()
sb.append(indent * 3).append("return null").newLine()
sb.append(indent * 2).append("}").newLine()
sb.append(indent * 1).append("}").newLine()
}
}
21 changes: 20 additions & 1 deletion src/main/kotlin/wu/seal/jsontokotlin/utils/Extensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package wu.seal.jsontokotlin.utils
import com.google.gson.JsonArray
import com.google.gson.JsonPrimitive
import wu.seal.jsontokotlin.model.classscodestruct.KotlinClass
import java.lang.StringBuilder
import java.util.regex.Pattern

/**
Expand Down Expand Up @@ -209,4 +210,22 @@ fun String.toAnnotationComments(indent: String): String {
}
}

fun String.addIndent(indent: String): String = this.lines().joinToString("\n") { if (it.isBlank()) it else "$indent$it" }
fun String.addIndent(indent: String): String = this.lines().joinToString("\n") { if (it.isBlank()) it else "$indent$it" }

operator fun String.times(count: Int): String {
if (count < 1) return ""
var i = 0
val string = this
val sb = StringBuilder().apply {
while (i < count) {
append(string)
i++
}
}
return sb.toString()
}

fun StringBuilder.newLine(): StringBuilder {
this.append("\n")
return this
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package extensions.yuan.varenyzc

import com.winterbe.expekt.should
import org.junit.Before
import org.junit.Test
import wu.seal.jsontokotlin.generateKotlinClassCode
import wu.seal.jsontokotlin.test.TestConfig

class BuildFromJsonObjectSupportTest {
private val json = """{"a":1,"b":"abc","c":true}"""

private val expectCode: String = """
data class Test(
@SerializedName("a")
val a: Int = 0, // 1
@SerializedName("b")
val b: String = "", // abc
@SerializedName("c")
val c: Boolean = false // true
) {

companion object {
@JvmStatic
fun buildFromJson(jsonObject: JSONObject?): Test? {

jsonObject?.run {
return Test(
optInt("a"),
optString("b"),
optBoolean("c")
)
}
return null
}
}
}
""".trimIndent()

@Before
fun setUp() {
TestConfig.setToTestInitState()
}

@Test
fun intercept() {
BuildFromJsonObjectSupport.getTestHelper().setConfig(BuildFromJsonObjectSupport.configKey, true.toString())
print(json.generateKotlinClassCode())
json.generateKotlinClassCode().should.be.equal(expectCode)
BuildFromJsonObjectSupport.getTestHelper().setConfig(BuildFromJsonObjectSupport.configKey, false.toString())
json.generateKotlinClassCode()
}
}
Loading