All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ecwid.apiclient.v3.responsefields.ResponseFieldsBuilder.kt Maven / Gradle / Ivy

package com.ecwid.apiclient.v3.responsefields

import com.ecwid.apiclient.v3.jsontransformer.JsonFieldName
import java.util.concurrent.ConcurrentHashMap
import kotlin.reflect.KAnnotatedElement
import kotlin.reflect.KClass
import kotlin.reflect.KProperty1
import kotlin.reflect.KType
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.javaField

/**
 * Generate responseFields parameter from data class
 */
class ResponseFieldsBuilder {
	private val cache = ConcurrentHashMap, ResponseFields>()

	/**
	 * Only kotlin data classes allowed
	 *
	 * @throws IllegalArgumentException if class is not kotlin data class
	 */
	inline fun  buildResponseFields(): ResponseFields {
		return buildResponseFields(T::class)
	}

	/**
	 * Only kotlin data classes allowed
	 *
	 * @throws IllegalArgumentException if [klass] is not kotlin data class
	 */
	fun buildResponseFields(klass: KClass<*>): ResponseFields {
		require(klass.isData) {
			"Only kotlin data class allowed: $klass"
		}

		return cache.getOrPut(klass) {
			val fields = klass.memberProperties.associate(this::parseProperty)
			if (fields.isEmpty()) {
				ResponseFields.All
			} else {
				ResponseFields(fields)
			}
		}
	}

	private fun parseProperty(property: KProperty1<*, *>): Pair {
		val fromAnnotation = buildFromAnnotation(property)
		if (fromAnnotation != null) {
			return buildPropertyName(property) to fromAnnotation
		}

		val returnType = property.returnType

		val typeArguments = returnType.arguments
		when {
			// Collection
			typeArguments.size == 1 && isCollectionType(returnType) -> {
				val genericArgument = typeArguments.first().type
					?: error("Not allowed type $returnType in property ${property.name} of class ${property::class.qualifiedName}")

				return buildPropertyName(property) to buildFieldByType(genericArgument)
			}

			// Map
			typeArguments.size == 2 && isMapType(returnType) -> {
				val genericArgument = typeArguments[1].type
					?: error("Not allowed type $returnType in property ${property.name} of class ${property::class.qualifiedName}")

				return buildPropertyName(property) to buildFieldByType(genericArgument)
			}
		}

		return buildPropertyName(property) to buildFieldByType(returnType)
	}

	private fun buildFieldByType(type: KType): ResponseFields.Field {
		val klass = type.classifier as? KClass<*>
			?: return ResponseFields.Field.All

		val fromAnnotation = buildFromAnnotation(klass)
		if (fromAnnotation != null) {
			return fromAnnotation
		}

		if (klass.isData) {
			val fields = buildResponseFields(klass).fields
			return if (fields.isEmpty()) {
				ResponseFields.Field.All
			} else {
				ResponseFields.Field(fields)
			}
		}

		return ResponseFields.Field.All
	}

	private fun isMapType(propertyType: KType): Boolean {
		val klass = propertyType.classifier as? KClass<*>
			?: return false

		return Map::class.java.isAssignableFrom(klass.java)
	}

	private fun isCollectionType(propertyType: KType): Boolean {
		val klass = propertyType.classifier as? KClass<*>
			?: return false

		return Collection::class.java.isAssignableFrom(klass.java)
	}

	private fun buildFromAnnotation(element: KAnnotatedElement): ResponseFields.Field? {
		val annotation = element.findAnnotation()
			?: return null

		return if (annotation.fields.isEmpty()) {
			ResponseFields.Field.All
		} else {
			val fieldsMap = annotation.fields.associateWith { ResponseFields.Field.All }
			ResponseFields.Field(fieldsMap)
		}
	}

	private fun buildPropertyName(property: KProperty1<*, *>): String {
		val annotation = property.javaField?.getAnnotation(JsonFieldName::class.java)
			?: return property.name

		return annotation.fieldName
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy