gsonpath.adapter.standard.interf.ModelInterfaceGenerator.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gsonpath-compiler Show documentation
Show all versions of gsonpath-compiler Show documentation
An annotation processor which generates Type Adapters for the Google Gson library
package gsonpath.adapter.standard.interf
import com.squareup.javapoet.*
import gsonpath.ProcessingException
import gsonpath.adapter.Constants.GENERATED_ANNOTATION
import gsonpath.adapter.Constants.NULL
import gsonpath.adapter.standard.model.FieldInfoFactory
import gsonpath.adapter.standard.model.FieldInfoFactory.InterfaceFieldInfo
import gsonpath.adapter.standard.model.FieldInfoFactory.InterfaceInfo
import gsonpath.adapter.util.writeFile
import gsonpath.compiler.generateClassName
import gsonpath.util.*
import javax.lang.model.element.Element
import javax.lang.model.element.Modifier
import javax.lang.model.element.TypeElement
class ModelInterfaceGenerator(
private val interfaceModelMetadataFactory: InterfaceModelMetadataFactory,
private val fileWriter: FileWriter) {
@Throws(ProcessingException::class)
fun handle(element: TypeElement): InterfaceInfo {
return createOutputClassName(element).let { outputClassName ->
TypeSpecExt.finalClassBuilder(outputClassName)
.addDetails(element, outputClassName)
}
}
private fun TypeSpec.Builder.addDetails(element: TypeElement, outputClassName: ClassName): InterfaceInfo {
addSuperinterface(ClassName.get(element))
addAnnotation(GENERATED_ANNOTATION)
val modelMetadataList = interfaceModelMetadataFactory.createMetadata(element)
addFields(modelMetadataList)
addConstructor(modelMetadataList)
addGetters(modelMetadataList)
addEqualsMethod(outputClassName, modelMetadataList)
addHashCodeMethod(modelMetadataList)
addToStringMethod(element, modelMetadataList)
writeFile(fileWriter, outputClassName.packageName())
return InterfaceInfo(outputClassName, modelMetadataList.map {
InterfaceFieldInfo(StandardElementInfo(it.enclosedElement),
it.typeName, it.returnTypeMirror, it.fieldName, it.methodName)
})
}
private fun TypeSpec.Builder.addFields(modelMetadataList: List) {
modelMetadataList.forEach {
field(it.fieldName, it.typeName) {
addModifiers(Modifier.PRIVATE, Modifier.FINAL)
}
}
}
private fun TypeSpec.Builder.addConstructor(modelMetadataList: List) = constructor {
addModifiers(Modifier.PUBLIC)
modelMetadataList.forEach { (typeName, fieldName) ->
addParameter(typeName, fieldName)
}
code {
modelMetadataList.forEach { (_, fieldName) ->
assign("this.$fieldName", fieldName)
}
}
}
private fun TypeSpec.Builder.addGetters(modelMetadataList: List) {
modelMetadataList.forEach {
overrideMethod(it.methodName) {
returns(it.typeName)
// Copy all annotations from the interface accessor method to the implementing classes accessor.
val annotationMirrors = it.enclosedElement.annotationMirrors
for (annotationMirror in annotationMirrors) {
addAnnotation(AnnotationSpec.get(annotationMirror))
}
code {
`return`(it.fieldName)
}
}
}
}
private fun TypeSpec.Builder.addEqualsMethod(outputClassName: ClassName, modelMetadataList: List) = overrideMethod("equals") {
returns(TypeName.BOOLEAN)
addParameter(TypeName.OBJECT, "o")
code {
`if`("this == o") {
`return`(TRUE)
}
`if`("o == null || getClass() != o.getClass()") {
`return`(FALSE)
}
newLine()
createVariable(outputClassName, EQUALS_OTHER_TYPE, "(\$T) o", outputClassName)
newLine()
modelMetadataList.forEach { (typeName, fieldName) ->
if (typeName.isPrimitive) {
`if`("$fieldName != $EQUALS_OTHER_TYPE.$fieldName") {
`return`(FALSE)
}
} else {
if (typeName is ArrayTypeName) {
`if`("!java.util.Arrays.equals($fieldName, $EQUALS_OTHER_TYPE.$fieldName)") {
`return`(FALSE)
}
} else {
`if`("$fieldName != $NULL ? !$fieldName.equals($EQUALS_OTHER_TYPE.$fieldName) : $EQUALS_OTHER_TYPE.$fieldName != $NULL") {
`return`(FALSE)
}
}
}
}
newLine()
`return`(TRUE)
}
}
private fun TypeSpec.Builder.addHashCodeMethod(modelMetadataList: List) = overrideMethod("hashCode") {
returns(TypeName.INT)
code {
// An optimisation for hash codes which prevents us creating too many temp long variables.
if (modelMetadataList.any { it.typeName == TypeName.DOUBLE }) {
addStatement("long $TEMP")
}
modelMetadataList.forEachIndexed { index, (typeName, fieldName) ->
val hashCodeLine: String = if (typeName.isPrimitive) {
// The allowed primitive types are: int, long, double, boolean
when (typeName) {
TypeName.INT -> fieldName
TypeName.LONG -> "(int) ($fieldName ^ ($fieldName >>> 32))"
TypeName.DOUBLE -> {
assign(TEMP, "java.lang.Double.doubleToLongBits($fieldName)")
"(int) ($TEMP ^ ($TEMP >>> 32))"
}
else -> // Last possible outcome in a boolean.
"($fieldName ? 1 : 0)"
}
} else {
if (typeName is ArrayTypeName) {
"java.util.Arrays.hashCode($fieldName)"
} else {
"$fieldName != $NULL ? $fieldName.hashCode() : 0"
}
}
if (index == 0) {
createVariable("int", HASH_CODE_RETURN_VALUE, hashCodeLine)
} else {
assign(HASH_CODE_RETURN_VALUE, "31 * $HASH_CODE_RETURN_VALUE + ($hashCodeLine)")
}
}
// If we have no elements, 'hashCodeReturnValue' won't be initialised!
if (modelMetadataList.isNotEmpty()) {
`return`(HASH_CODE_RETURN_VALUE)
} else {
`return`("0")
}
}
}
private fun TypeSpec.Builder.addToStringMethod(element: TypeElement, modelMetadataList: List) = overrideMethod("toString") {
returns(TypeName.get(String::class.java))
val className = ClassName.get(element).simpleName()
code {
add("""return "$className{" +""")
newLine()
indent()
add("\"")
modelMetadataList.forEachIndexed { index, (typeName, fieldName) ->
// Add to the toString method.
if (index > 0) {
add("\", ")
}
if (typeName is ArrayTypeName) {
add("""$fieldName=" + java.util.Arrays.toString($fieldName) +""")
} else {
add("""$fieldName=" + $fieldName +""")
}
newLine()
}
addStatement("'}'")
}
}
private fun createOutputClassName(element: TypeElement): ClassName {
return ClassName.get(element).let {
ClassName.get(it.packageName(), generateClassName(it, "GsonPathModel"))
}
}
private class StandardElementInfo(override val underlyingElement: Element) : FieldInfoFactory.ElementInfo {
override fun getAnnotation(annotationClass: Class): T? {
return underlyingElement.getAnnotationEx(annotationClass)
}
override val annotationNames: List
get() {
return underlyingElement.annotationMirrors.map { it ->
it.annotationType.asElement().simpleName.toString()
}
}
}
private companion object {
private const val TRUE = "true"
private const val FALSE = "false"
private const val TEMP = "temp"
private const val HASH_CODE_RETURN_VALUE = "hashCodeReturnValue"
private const val EQUALS_OTHER_TYPE = "equalsOtherType"
}
}