Skip to content

Commit

Permalink
Merge branch 'mscheong01:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
mscheong01 authored Jun 19, 2023
2 parents 821a450 + 940f7c3 commit fc6b829
Show file tree
Hide file tree
Showing 11 changed files with 329 additions and 19 deletions.
4 changes: 4 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ subprojects {
url = uri("artifactregistry://asia-northeast3-maven.pkg.dev/mp-artifact-registry-aa49/qanda-packages")
}
}

tasks.withType<Test> {
useJUnitPlatform()
}
}

configure<org.jlleitschuh.gradle.ktlint.KtlintExtension> {
Expand Down
1 change: 1 addition & 0 deletions example/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ dependencies {
testImplementation("javax.annotation:javax.annotation-api:1.3.2")
// https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api
testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.2")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.2")
// https://mvnrepository.com/artifact/org.assertj/assertj-core
testImplementation("org.assertj:assertj-core:3.24.2")
testImplementation("io.grpc:grpc-testing:${rootProject.ext["grpcJavaVersion"]}")
Expand Down
1 change: 1 addition & 0 deletions generator/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ dependencies {
testImplementation("javax.annotation:javax.annotation-api:1.3.2")
// https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api
testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.2")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.2")
// https://mvnrepository.com/artifact/org.assertj/assertj-core
testImplementation("org.assertj:assertj-core:3.24.2")
testImplementation("io.grpc:grpc-testing:${rootProject.ext["grpcJavaVersion"]}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@ import io.github.mscheong01.krotodc.template.TransformTemplateWithImports
import io.github.mscheong01.krotodc.util.MAP_ENTRY_VALUE_FIELD_NUMBER
import io.github.mscheong01.krotodc.util.capitalize
import io.github.mscheong01.krotodc.util.endControlFlowWithComma
import io.github.mscheong01.krotodc.util.escapeIfNecessary
import io.github.mscheong01.krotodc.util.fieldNameToJsonName
import io.github.mscheong01.krotodc.util.isHandledPreDefinedType
import io.github.mscheong01.krotodc.util.isKrotoDCOptional
import io.github.mscheong01.krotodc.util.isPredefinedType
import io.github.mscheong01.krotodc.util.javaFieldName
import io.github.mscheong01.krotodc.util.krotoDCPackage
import io.github.mscheong01.krotodc.util.krotoDCTypeName
import io.github.mscheong01.krotodc.util.protobufJavaTypeName
Expand All @@ -52,9 +54,14 @@ class MessageToDataClassFunctionGenerator : FunSpecGenerator<Descriptor> {

for (oneOf in descriptor.realOneofs) {
val oneOfJsonName = fieldNameToJsonName(oneOf.name)
functionBuilder.beginControlFlow("%L = when (%LCase)", oneOfJsonName, oneOfJsonName)
functionBuilder.beginControlFlow(
"%L = when (%LCase)",
oneOfJsonName.escapeIfNecessary(),
oneOfJsonName
)
for (field in oneOf.fields) {
val fieldName = field.jsonName
val dataClassFieldName = field.jsonName
val protoFieldName = field.javaFieldName
val (template, downStreamImports) = transformCodeTemplate(field)
val oneOfDataClassName = ClassName(
oneOf.file.krotoDCPackage,
Expand All @@ -69,8 +76,8 @@ class MessageToDataClassFunctionGenerator : FunSpecGenerator<Descriptor> {
oneOfJsonName.capitalize(),
field.name.uppercase(),
oneOfDataClassName.canonicalName,
field.jsonName,
template.safeCall(fieldName)
dataClassFieldName.escapeIfNecessary(),
template.safeCall(protoFieldName.escapeIfNecessary())
)
imports.addAll(downStreamImports)
}
Expand All @@ -84,38 +91,39 @@ class MessageToDataClassFunctionGenerator : FunSpecGenerator<Descriptor> {
continue
}

val fieldName = field.jsonName
val dataClassFieldName = field.jsonName
val protoFieldName = field.javaFieldName
val optional = field.isKrotoDCOptional
functionBuilder.addCode("%L = ", fieldName)
functionBuilder.addCode("%L = ", dataClassFieldName.escapeIfNecessary())
if (optional) {
functionBuilder.beginControlFlow("if (has${fieldName.capitalize()}())")
functionBuilder.beginControlFlow("if (has${protoFieldName.capitalize()}())")
}

val codeWithImports = if (field.isMapField) {
val valueField = field.messageType.findFieldByNumber(MAP_ENTRY_VALUE_FIELD_NUMBER)
val (template, downStreamImports) = transformCodeTemplate(valueField)
val mapCodeBlock = if (template.value == "%L") {
CodeBlock.of("%LMap", fieldName)
CodeBlock.of("%LMap", protoFieldName)
} else {
CodeBlock.of(
"%LMap.mapValues { %L }",
fieldName,
protoFieldName,
template.safeCall("it.value")
)
}
CodeWithImports.of(mapCodeBlock, downStreamImports)
} else if (field.isRepeated) {
val (template, downStreamImports) = transformCodeTemplate(field)
val repeatedCodeBlock = if (template.value == "%L") {
CodeBlock.of("%LList", fieldName)
CodeBlock.of("%LList", protoFieldName)
} else {
CodeBlock.of("%LList.map { %L }", fieldName, template.safeCall("it"))
CodeBlock.of("%LList.map { %L }", protoFieldName, template.safeCall("it"))
}
CodeWithImports.of(repeatedCodeBlock, downStreamImports)
} else {
val (template, downStreamImports) = transformCodeTemplate(field)
CodeWithImports.of(
template.safeCall(fieldName),
template.safeCall(protoFieldName.escapeIfNecessary()),
downStreamImports
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ import io.github.mscheong01.krotodc.specgenerators.FunSpecGenerator
import io.github.mscheong01.krotodc.template.TransformTemplateWithImports
import io.github.mscheong01.krotodc.util.MAP_ENTRY_VALUE_FIELD_NUMBER
import io.github.mscheong01.krotodc.util.capitalize
import io.github.mscheong01.krotodc.util.escapeIfNecessary
import io.github.mscheong01.krotodc.util.fieldNameToJsonName
import io.github.mscheong01.krotodc.util.isHandledPreDefinedType
import io.github.mscheong01.krotodc.util.isKrotoDCOptional
import io.github.mscheong01.krotodc.util.isPredefinedType
import io.github.mscheong01.krotodc.util.javaFieldName
import io.github.mscheong01.krotodc.util.krotoDCPackage
import io.github.mscheong01.krotodc.util.krotoDCTypeName
import io.github.mscheong01.krotodc.util.protobufJavaTypeName
Expand All @@ -53,7 +55,7 @@ class MessageToProtoFunctionGenerator : FunSpecGenerator<Descriptor> {

for (oneOf in descriptor.realOneofs) {
val oneOfJsonName = fieldNameToJsonName(oneOf.name)
functionBuilder.beginControlFlow("when (%L)", oneOfJsonName)
functionBuilder.beginControlFlow("when (%L)", oneOfJsonName.escapeIfNecessary())
for (field in oneOf.fields) {
val oneOfFieldDataClassName = ClassName(
oneOf.file.krotoDCPackage,
Expand All @@ -65,8 +67,17 @@ class MessageToProtoFunctionGenerator : FunSpecGenerator<Descriptor> {
functionBuilder.beginControlFlow("is %L ->", oneOfFieldDataClassName)
val (template, downStreamImports) = transformCodeTemplate(field)
functionBuilder.addStatement(
"set${field.jsonName.capitalize()}(%L)",
CodeBlock.of("%L", template.safeCall(CodeBlock.of("%L.%L", oneOfJsonName, field.jsonName)))
"set${field.javaFieldName.capitalize()}(%L)",
CodeBlock.of(
"%L",
template.safeCall(
CodeBlock.of(
"%L.%L",
oneOfJsonName.escapeIfNecessary(),
field.jsonName.escapeIfNecessary()
)
)
)
)

functionBuilder.endControlFlow()
Expand All @@ -80,7 +91,7 @@ class MessageToProtoFunctionGenerator : FunSpecGenerator<Descriptor> {
if (field.name in descriptor.realOneofs.map { it.fields }.flatten().map { it.name }.toSet()) {
continue
}
val fieldName = "this@toProto.${field.jsonName}"
val fieldName = "this@toProto.${field.jsonName.escapeIfNecessary()}"
val optional = field.isKrotoDCOptional
if (optional) {
functionBuilder.beginControlFlow("if ($fieldName != null)")
Expand Down Expand Up @@ -111,9 +122,9 @@ class MessageToProtoFunctionGenerator : FunSpecGenerator<Descriptor> {
)
}
val accessorMethodName = when {
field.isMapField -> "putAll${field.jsonName.capitalize()}"
field.isRepeated -> "addAll${field.jsonName.capitalize()}"
else -> "set${field.jsonName.capitalize()}"
field.isMapField -> "putAll${field.javaFieldName.capitalize()}"
field.isRepeated -> "addAll${field.javaFieldName.capitalize()}"
else -> "set${field.javaFieldName.capitalize()}"
}
imports.addAll(codeWithImports.imports)
functionBuilder.addCode(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,40 @@ val Descriptor.toDataClassImport: Import
listOf("toDataClass")
)
}

/**
* beware: does not escape Kotlin keywords
*/
val FieldDescriptor.javaFieldName: String
get() {
val jsonName = this.jsonName
/**
* protobuf-java escapes special fields in order to avoid name clashes with Java/Protobuf keywords
* @see https://github.com/protocolbuffers/protobuf/blob/2cf94fafe39eeab44d3ab83898aabf03ff930d7a/java/core/src/main/java/com/google/protobuf/DescriptorMessageInfoFactory.java#L629C1-L648
*/
return if (PROTOBUF_JAVA_SPECIAL_FIELD_NAMES.contains(jsonName.capitalize())) {
jsonName + "_"
} else {
jsonName
}
}

/**
* @see https://github.com/protocolbuffers/protobuf/blob/2cf94fafe39eeab44d3ab83898aabf03ff930d7a/java/core/src/main/java/com/google/protobuf/DescriptorMessageInfoFactory.java#L72
*/
val PROTOBUF_JAVA_SPECIAL_FIELD_NAMES = setOf(
// java.lang.Object:
"Class",
// com.google.protobuf.MessageLiteOrBuilder:
"DefaultInstanceForType",
// com.google.protobuf.MessageLite:
"ParserForType",
"SerializedSize",
// com.google.protobuf.MessageOrBuilder:
"AllFields",
"DescriptorForType",
"InitializationErrorString",
"UnknownFields",
// obsolete. kept for backwards compatibility of generated code
"CachedSize"
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright 2023 Minsoo Cheong
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package io.github.mscheong01.krotodc.util

import com.squareup.kotlinpoet.CodeBlock

// escape a string if necessary using Kotlinpoet API
fun String.escapeIfNecessary(): String {
return CodeBlock.of("%N", this).toString()
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// limitations under the License.
package io.github.mscheong01.krotodc

import com.example.importtest.OuterClassNameTestProto
import com.google.protobuf.ByteString
import io.github.mscheong01.importtest.ImportFromOtherFileTest.ImportTestMessage
import io.github.mscheong01.importtest.krotodc.importtestmessage.toDataClass
Expand Down Expand Up @@ -293,6 +294,7 @@ class ConversionTest {
val proto = ImportTestMessage.newBuilder()
.setImportedNestedMessage(TopLevelMessage.NestedMessage.newBuilder().setName("test").build())
.setImportedPerson(Person.newBuilder().setName("John").setAge(30).build())
.setImportedSimpleMessage(OuterClassNameTestProto.SimpleMessage.newBuilder().setName("test").build())
.build()
val kroto = proto.toDataClass()
Assertions.assertThat(kroto.importedNestedMessage).isEqualTo(
Expand Down
Loading

0 comments on commit fc6b829

Please sign in to comment.