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

WIP #242

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open

WIP #242

Changes from all 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
WIP
LachlanMcKee committed Dec 9, 2019

Verified

This commit was signed with the committer’s verified signature. The key has expired.
BridgeAR Ruben Bridgewater
commit 14fcb783822ed5f5d99a82c351afcf37a2f39326
2 changes: 1 addition & 1 deletion compiler/base/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
POM_ARTIFACT_ID=gsonpath-compiler-base
POM_NAME=gsonpath-compiler-base
VERSION_NAME=1.5.1
VERSION_NAME=1.5.2
POM_PACKAGING=jar
38 changes: 27 additions & 11 deletions compiler/base/src/main/java/gsonpath/util/TypeHandler.kt
Original file line number Diff line number Diff line change
@@ -3,18 +3,15 @@ package gsonpath.util
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.TypeName
import javax.annotation.processing.ProcessingEnvironment
import javax.lang.model.element.Element
import javax.lang.model.element.ElementKind
import javax.lang.model.element.Modifier
import javax.lang.model.element.TypeElement
import javax.lang.model.element.*
import javax.lang.model.type.DeclaredType
import javax.lang.model.type.ExecutableType
import javax.lang.model.type.TypeMirror
import kotlin.reflect.KClass

interface TypeHandler {
fun getTypeName(typeMirror: TypeMirror): TypeName?
fun getClassName(typeMirror: TypeMirror): TypeName?
fun getTypeName(typeMirror: TypeMirror): TypeName
fun getClassName(typeMirror: TypeMirror): TypeName
fun isSubtype(t1: TypeMirror, t2: TypeMirror): Boolean
fun asElement(t: TypeMirror): Element?
fun getAllMembers(typeElement: TypeElement): List<Element>
@@ -30,14 +27,22 @@ data class FieldElementContent(
)

data class MethodElementContent(
val element: Element,
val generifiedElement: ExecutableType
val element: ExecutableElement,
val methodName: String,
val returnTypeMirror: TypeMirror,
val returnTypeName: TypeName,
val parameterElementContents: List<ParameterElementContent>
)

data class ParameterElementContent(
val element: VariableElement,
val typeName: TypeName
)

class ProcessorTypeHandler(private val processingEnv: ProcessingEnvironment) : TypeHandler {
override fun getTypeName(typeMirror: TypeMirror): TypeName? = TypeName.get(typeMirror)
override fun getTypeName(typeMirror: TypeMirror): TypeName = TypeName.get(typeMirror)

override fun getClassName(typeMirror: TypeMirror): TypeName? = ClassName.get(typeMirror)
override fun getClassName(typeMirror: TypeMirror): TypeName = ClassName.get(typeMirror)

override fun asElement(t: TypeMirror): Element? {
return processingEnv.typeUtils.asElement(t)
@@ -78,7 +83,18 @@ class ProcessorTypeHandler(private val processingEnv: ProcessingEnvironment) : T
true
}
}
.map { MethodElementContent(it, getGenerifiedTypeMirror(typeElement, it) as ExecutableType) }
.map { methodElement ->
val generifiedMethodMirror = getGenerifiedTypeMirror(typeElement, methodElement) as ExecutableType
MethodElementContent(
methodElement as ExecutableElement,
methodElement.simpleName.toString(),
generifiedMethodMirror.returnType,
getTypeName(generifiedMethodMirror.returnType),
methodElement.parameters.map {
ParameterElementContent(it, getTypeName(it.asType()))
}
)
}
.toList()
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package gsonpath.adapter.common

import gsonpath.ProcessingException
import gsonpath.adapter.util.NullableUtil
import gsonpath.util.MethodElementContent

class GsonSubTypeFieldInfoMapper {
fun mapToFieldInfo(
methodContent: MethodElementContent,
jsonKeys: Array<String>): List<GsonSubTypeFieldInfo> {

val parameters = methodContent.parameterElementContents
if (parameters.size != jsonKeys.size) {
throw ProcessingException("The parameters size does not match the json keys size", methodContent.element)
}

return parameters.zip(jsonKeys)
.mapIndexed { index, (parameter, key) ->
val nonNullAnnotationExists = parameter.element.annotationMirrors
.any { NullableUtil.isNullableKeyword(it.annotationType.asElement().simpleName.toString()) }

val nullable = !nonNullAnnotationExists && !parameter.typeName.isPrimitive

GsonSubTypeFieldInfo(key, "subTypeElement$index", parameter.typeName, nullable)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package gsonpath.adapter.common

import gsonpath.GsonSubtypeGetter
import gsonpath.ProcessingException
import gsonpath.util.MethodElementContent
import gsonpath.util.TypeHandler
import javax.lang.model.element.TypeElement

class GsonSubTypeGetterMapper(private val typeHandler: TypeHandler) {
fun mapElementToGetterMethod(classElement: TypeElement): MethodElementContent {
return typeHandler.getMethods(classElement)
.filter { it.element.getAnnotation(GsonSubtypeGetter::class.java) != null }
.let {
when {
it.isEmpty() -> throw ProcessingException("An @GsonSubtypeGetter method must be defined. See the annotation for more information", classElement)
it.size > 1 -> throw ProcessingException("Only one @GsonSubtypeGetter method may exist", classElement)
else -> it.first()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package gsonpath.adapter.common

import com.squareup.javapoet.ClassName
import com.squareup.javapoet.ParameterizedTypeName
import com.squareup.javapoet.TypeName
import com.squareup.javapoet.WildcardTypeName
import gsonpath.ProcessingException
import gsonpath.util.MethodElementContent
import javax.lang.model.element.TypeElement

class SubTypeGetterValidator {
fun validateGsonSubtypeGetterMethod(
classElement: TypeElement,
methodContent: MethodElementContent): ProcessingException? {

val elementTypeName = TypeName.get(classElement.asType())
val expectedReturnType = ParameterizedTypeName.get(
ClassName.get(Class::class.java), WildcardTypeName.subtypeOf(elementTypeName))

return if (methodContent.returnTypeName != expectedReturnType) {
ProcessingException("Incorrect return type for @GsonSubtypeGetter method. It must be " +
"Class<? extends ${classElement.simpleName}>", methodContent.element)
} else {
null
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package gsonpath.adapter.common

import gsonpath.ProcessingException
import javax.lang.model.element.TypeElement

class SubTypeJsonKeysValidator {

fun validateJsonKeys(classElement: TypeElement, jsonKeys: Array<String>): ProcessingException? {
if (jsonKeys.isEmpty()) {
return ProcessingException("At least one json key must be defined for GsonSubType", classElement)
}

if (jsonKeys.any { it.isBlank() }) {
return ProcessingException("A blank json key is not valid for GsonSubType", classElement)
}

return jsonKeys.groupingBy { it }
.eachCount()
.filter { it.value > 1 }
.keys
.firstOrNull()
?.let {
throw ProcessingException("The json key '\"$it\"' appears more than once", classElement)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,109 +1,39 @@
package gsonpath.adapter.common

import com.squareup.javapoet.ClassName
import com.squareup.javapoet.ParameterizedTypeName
import com.squareup.javapoet.TypeName
import com.squareup.javapoet.WildcardTypeName
import gsonpath.GsonSubtype
import gsonpath.GsonSubtypeGetter
import gsonpath.ProcessingException
import gsonpath.adapter.util.NullableUtil
import gsonpath.model.FieldType
import gsonpath.util.MethodElementContent
import gsonpath.util.TypeHandler
import javax.lang.model.element.ExecutableElement
import javax.lang.model.element.TypeElement

interface SubTypeMetadataFactory {
fun getGsonSubType(
gsonSubType: GsonSubtype,
fieldType: FieldType.Other,
fieldName: String,
element: TypeElement): SubTypeMetadata
fun getGsonSubType(gsonSubType: GsonSubtype, element: TypeElement): SubTypeMetadata
}

class SubTypeMetadataFactoryImpl(
private val typeHandler: TypeHandler) : SubTypeMetadataFactory {
private val gsonSubTypeGetterMapper: GsonSubTypeGetterMapper,
private val gsonSubTypeFieldInfoMapper: GsonSubTypeFieldInfoMapper,
private val subTypeJsonKeysValidator: SubTypeJsonKeysValidator,
private val subTypeGetterValidator: SubTypeGetterValidator) : SubTypeMetadataFactory {

override fun getGsonSubType(
gsonSubType: GsonSubtype,
fieldType: FieldType.Other,
fieldName: String,
element: TypeElement): SubTypeMetadata {
override fun getGsonSubType(gsonSubType: GsonSubtype, element: TypeElement): SubTypeMetadata {
subTypeJsonKeysValidator
.validateJsonKeys(element, gsonSubType.jsonKeys)
.throwIfNotNull()

val jsonKeys = gsonSubType.jsonKeys
validateJsonKeys(element, jsonKeys)
val methodContent = gsonSubTypeGetterMapper.mapElementToGetterMethod(element)

return getGsonSubtypeGetterMethod(element)
.also { methodContent -> validateGsonSubtypeGetterMethod(element, methodContent) }
.let { methodContent -> createSubTypeMetadata(methodContent, jsonKeys) }
}

private fun validateJsonKeys(classElement: TypeElement, jsonKeys: Array<String>) {
if (jsonKeys.isEmpty()) {
throw ProcessingException("At least one json key must be defined for GsonSubType", classElement)
}

if (jsonKeys.any { it.isBlank() }) {
throw ProcessingException("A blank json key is not valid for GsonSubType", classElement)
}

jsonKeys.groupingBy { it }
.eachCount()
.filter { it.value > 1 }
.keys
.firstOrNull()
.let {
if (it != null) {
throw ProcessingException("The json key '\"$it\"' appears more than once", classElement)
}
}
}

private fun getGsonSubtypeGetterMethod(classElement: TypeElement): MethodElementContent {
return typeHandler.getMethods(classElement)
.filter { it.element.getAnnotation(GsonSubtypeGetter::class.java) != null }
.let {
when {
it.isEmpty() -> throw ProcessingException("An @GsonSubtypeGetter method must be defined. See the annotation for more information", classElement)
it.size > 1 -> throw ProcessingException("Only one @GsonSubtypeGetter method may exist", classElement)
else -> it.first()
}
}
}

private fun validateGsonSubtypeGetterMethod(classElement: TypeElement, methodContent: MethodElementContent) {
val actualReturnType = typeHandler.getTypeName(methodContent.generifiedElement.returnType)
val elementTypeName = TypeName.get(classElement.asType())
val expectedReturnType = ParameterizedTypeName.get(ClassName.get(Class::class.java), WildcardTypeName.subtypeOf(elementTypeName))
subTypeGetterValidator
.validateGsonSubtypeGetterMethod(element, methodContent)
.throwIfNotNull()

if (actualReturnType != expectedReturnType) {
throw ProcessingException("Incorrect return type for @GsonSubtypeGetter method. It must be Class<? extends ${classElement.simpleName}>", methodContent.element)
}
return SubTypeMetadata(
gsonSubTypeFieldInfoMapper.mapToFieldInfo(methodContent, gsonSubType.jsonKeys),
methodContent.methodName
)
}

private fun createSubTypeMetadata(methodContent: MethodElementContent, jsonKeys: Array<String>): SubTypeMetadata {
val executableType = methodContent.element as ExecutableElement

val parameters = executableType.parameters
if (parameters.size != jsonKeys.size) {
throw ProcessingException("The parameters size does not match the json keys size", methodContent.element)
private fun ProcessingException?.throwIfNotNull() {
if (this != null) {
throw this
}

val fieldInfoList = parameters.zip(jsonKeys)
.mapIndexed { index, (parameter, key) ->
val nonNullAnnotationExists = parameter.annotationMirrors
.any { NullableUtil.isNullableKeyword(it.annotationType.asElement().simpleName.toString()) }

val parameterTypeName = TypeName.get(parameter.asType())
val nullable = !nonNullAnnotationExists && !parameterTypeName.isPrimitive

GsonSubTypeFieldInfo(key, "subTypeElement$index", parameterTypeName, nullable)
}

return SubTypeMetadata(
fieldInfoList,
methodContent.element.simpleName.toString()
)
}
}
Original file line number Diff line number Diff line change
@@ -4,10 +4,7 @@ import com.squareup.javapoet.TypeName
import gsonpath.ProcessingException
import gsonpath.util.MethodElementContent
import gsonpath.util.TypeHandler
import javax.lang.model.element.Element
import javax.lang.model.element.TypeElement
import javax.lang.model.type.ExecutableType
import javax.lang.model.type.TypeMirror

class InterfaceModelMetadataFactory(private val typeHandler: TypeHandler) {

@@ -18,32 +15,25 @@ class InterfaceModelMetadataFactory(private val typeHandler: TypeHandler) {
private fun createMetadata(methodElementContent: MethodElementContent): InterfaceModelMetadata {
val methodElement = methodElementContent.element

// Ensure that any generics have been converted into their actual return types.
val returnTypeMirror: TypeMirror = methodElementContent.generifiedElement.returnType
val typeName = typeHandler.getTypeName(returnTypeMirror)

if (typeName == null || typeName == TypeName.VOID) {
val typeName = methodElementContent.returnTypeName
if (typeName == TypeName.VOID) {
throw ProcessingException("Gson Path interface methods must have a return type", methodElement)
}

(methodElement.asType() as ExecutableType).let {
if (it.parameterTypes.isNotEmpty()) {
throw ProcessingException("Gson Path interface methods must not have parameters", methodElement)
}
if (methodElementContent.parameterElementContents.isNotEmpty()) {
throw ProcessingException("Gson Path interface methods must not have parameters", methodElement)
}

return InterfaceModelMetadata(typeName, methodElement.getFieldName(), methodElement,
methodElement.getMethodName(), returnTypeMirror)
return InterfaceModelMetadata(typeName, methodElementContent.getFieldName(), methodElement,
methodElementContent.methodName, methodElementContent.returnTypeMirror)
}

private fun Element.getMethodName() = simpleName.toString()

/**
* Transform the method name into the field name by removing the first camel-cased portion.
* e.g. 'getName' becomes 'name'
*/
private fun Element.getFieldName() = getMethodName().let { methodName ->
methodName.indexOfFirst(Char::isUpperCase)
private fun MethodElementContent.getFieldName(): String {
return methodName.indexOfFirst(Char::isUpperCase)
.takeIf { it != -1 }
?.let { methodName[it].toLowerCase() + methodName.substring(it + 1) }
?: methodName
Original file line number Diff line number Diff line change
@@ -14,7 +14,6 @@ import gsonpath.adapter.util.AdapterFactoryUtil.getAnnotatedModelElements
import gsonpath.adapter.util.writeFile
import gsonpath.compiler.generateClassName
import gsonpath.dependencies.Dependencies
import gsonpath.model.FieldType
import gsonpath.util.Logger
import gsonpath.util.TypeSpecExt
import gsonpath.util.constructor
@@ -43,14 +42,7 @@ object SubTypeAdapterFactory : AdapterFactory {
logger.printMessage("Generating TypeAdapter ($element)")

val typeName = ClassName.get(element)
val subTypeMetadata = dependencies.subTypeMetadataFactory.getGsonSubType(
gsonSubtype,
FieldType.Other(
typeName = typeName,
elementTypeMirror = element.asType()
),
"Type",
element)
val subTypeMetadata = dependencies.subTypeMetadataFactory.getGsonSubType(gsonSubtype, element)

return GsonSubTypeFactory.createSubTypeMetadata(typeName, subTypeMetadata)
.let { result ->
Loading