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

org.apache.tinkerpop.gremlin.ogm.reflection.ObjectDescription.kt Maven / Gradle / Ivy

There is a newer version: 0.21.0
Show newest version
package org.apache.tinkerpop.gremlin.ogm.reflection


import org.apache.tinkerpop.gremlin.ogm.annotations.*
import org.apache.tinkerpop.gremlin.ogm.elements.Edge
import org.apache.tinkerpop.gremlin.ogm.elements.Element
import org.apache.tinkerpop.gremlin.ogm.elements.Vertex
import org.apache.tinkerpop.gremlin.ogm.exceptions.*
import org.apache.tinkerpop.gremlin.ogm.extensions.filterNullValues
import org.apache.tinkerpop.gremlin.ogm.extensions.nestedPropertyDelimiter
import org.apache.tinkerpop.gremlin.ogm.mappers.EdgeDeserializer.Companion.idTag
import org.apache.tinkerpop.gremlin.ogm.mappers.PropertyBiMapper
import org.apache.tinkerpop.gremlin.ogm.mappers.SerializedProperty
import org.apache.tinkerpop.gremlin.ogm.paths.relationships.Relationship
import kotlin.reflect.*
import kotlin.reflect.full.*

/**
 * Describes information about an object that is registered to be persisted to a graph either as a
 * vertex, edge or nested object.
 */
sealed class ObjectDescription(kClass: KClass) {

    /**
     * The constructor for the object that can be called with the parameters of the property description's +
     * nullConstructorParameters
     */
    val constructor: KFunction = kClass.constructor()

    /**
     * The properties of T that can be mapped to properties of a vertex.
     * The keys of the map are used as keys for the vertex properties.
     */
    val properties: Map> = kClass.properties()

    /**
     * The parameters for the primary constructor that should be called with null as their value.
     * All non-nullable, non-optional parameters will be to the properties map, however, nullable
     * non-optional properties that are not to the properties map (aka transient) must still be passed
     * to the constructor with null as their value.
     */
    val nullConstructorParameters: Collection = constructor.nullParameters()
}

/**
 * Contains the reflection information needed to map an object to/from a graph element (vertex or edge).
 */
sealed class ElementDescription(kClass: KClass) : ObjectDescription(kClass) {

    /**
     * The label of the element as stored to the graph
     */
    val label: String = kClass.label()

    /**
     * The property description for the id of the element
     */
    val id: PropertyDescription = kClass.idPropertyDescription(constructor)
}

/**
 * Contains the reflection information needed to map an object to/from a vertex.
 */
class VertexDescription(kClass: KClass) : ElementDescription(kClass)

/**
 * Contains the reflection information needed to map an object to/from an edge.
 */
class EdgeDescription>(

        /**
         * The relationship for this edge
         */
        val relationship: Relationship,

        kClass: KClass

) : ElementDescription(kClass) {

    /**
     * The parameter for the 'to' vertex of the edge.
     */
    val toVertex: KParameter = kClass.toVertexPropertyDescription(constructor)

    /**
     * The property description for the 'from' vertex of the edge.
     */
    val fromVertex: KParameter = kClass.fromVertexPropertyDescription(constructor)
}

/**
 * Describes information about an object that is registered to be persisted to a graph as a nested object.
 */
class ObjectPropertyDescription(kClass: KClass) : ObjectDescription(kClass)

private fun  KClass.constructor(): KFunction = primaryConstructor
        ?: throw PrimaryConstructorMissing(this)

private fun  KClass.label(): String = findAnnotation()?.label
        ?: throw ElementAnnotationMissing(this)

private fun > KClass.toVertexPropertyDescription(constructor: KFunction): KParameter {
    val annotatedToVertexParams = constructor.parameters.filter { param -> param.findAnnotation() != null }
    if (annotatedToVertexParams.size > 1) throw DuplicateToVertex(this)
    if (annotatedToVertexParams.isEmpty()) throw ToVertexParameterMissing(this)
    val annotatedToVertexParam = annotatedToVertexParams.single()
    if (annotatedToVertexParam.findAnnotation() != null) throw MapperUnsupported(annotatedToVertexParam)
    if (annotatedToVertexParam.findAnnotation() != null) throw ConflictingAnnotations(this, annotatedToVertexParam)
    if (annotatedToVertexParam.findAnnotation() != null) throw ConflictingAnnotations(this, annotatedToVertexParam)
    if (annotatedToVertexParam.findAnnotation() != null) throw ConflictingAnnotations(this, annotatedToVertexParam)
    if (annotatedToVertexParam.type.isMarkedNullable) throw NullableVertexParam(this, annotatedToVertexParam)
    return annotatedToVertexParam
}

private fun > KClass.fromVertexPropertyDescription(constructor: KFunction): KParameter {
    val annotatedFromVertexParams = constructor.parameters.filter { param -> param.findAnnotation() != null }
    if (annotatedFromVertexParams.size > 1) throw DuplicateFromVertex(this)
    if (annotatedFromVertexParams.size != 1) throw FromVertexParameterMissing(this)
    val annotatedFromVertexParam = annotatedFromVertexParams.single()
    if (annotatedFromVertexParam.findAnnotation() != null) throw MapperUnsupported(annotatedFromVertexParam)
    if (annotatedFromVertexParam.findAnnotation() != null) throw ConflictingAnnotations(this, annotatedFromVertexParam)
    if (annotatedFromVertexParam.findAnnotation() != null) throw ConflictingAnnotations(this, annotatedFromVertexParam)
    if (annotatedFromVertexParam.findAnnotation() != null) throw ConflictingAnnotations(this, annotatedFromVertexParam)
    if (annotatedFromVertexParam.type.isMarkedNullable) throw NullableVertexParam(this, annotatedFromVertexParam)
    return annotatedFromVertexParam
}

private fun  KClass.idPropertyDescription(constructor: KFunction): PropertyDescription {
    val memberProperties = memberProperties
    val memberPropertiesByName = memberProperties.associateBy { property -> property.name }
    val annotatedIDProperties = memberProperties.filter { property -> property.findAnnotation() != null }
    if (annotatedIDProperties.size > 1) throw DuplicateIDProperty(this)
    val annotatedIDParams = constructor.parameters.filter { param -> param.findAnnotation() != null }
    if (annotatedIDParams.size != 1) throw IDParameterRequired(this)
    val annotatedIDParam = annotatedIDParams.single()
    val idProperty = annotatedIDProperties.singleOrNull()
            ?: memberPropertiesByName[annotatedIDParam.name]
            ?: throw IDPropertyRequired(this)
    if (annotatedIDParam.findAnnotation() != null) throw MapperUnsupported(annotatedIDParam)
    if (annotatedIDParam.findAnnotation() != null) throw ConflictingAnnotations(this, annotatedIDParam)
    if (annotatedIDParam.findAnnotation() != null) throw ConflictingAnnotations(this, annotatedIDParam)
    if (annotatedIDParam.findAnnotation() != null) throw ConflictingAnnotations(this, annotatedIDParam)
    if (!annotatedIDParam.type.isMarkedNullable) throw NonNullableID(this, annotatedIDParam)
    return PropertyDescription(annotatedIDParam, idProperty, null)
}

internal fun KParameter.findMapper(): PropertyBiMapper? {
    val mapperAnnotation = findAnnotation() ?: return null
    val mapperInputType = mapperAnnotation.kClass.supertypes.single {
        val mapperAnnotationSuperClass = it.classifier as? KClass<*>
        mapperAnnotationSuperClass != null && mapperAnnotationSuperClass.isSubclassOf(PropertyBiMapper::class)
    }.arguments.first().type
    verifyClassifiersAreCompatible(type.classifier, mapperInputType?.classifier)
    @Suppress("UNCHECKED_CAST")
    return mapperAnnotation.kClass.createInstance() as? PropertyBiMapper
}

private fun  KClass.properties(): Map> {
    val memberProperties = memberProperties
    val annotatedMemberProperties = memberProperties
            .associate { property -> property to property.findAnnotation() }
            .filterNullValues()
    val memberPropertiesByKey = annotatedMemberProperties.entries.associate { it.value.key to it.key }
    if (memberPropertiesByKey.size != annotatedMemberProperties.size) throw DuplicatePropertyName(this)
    val memberPropertiesByName = memberProperties.associateBy { property -> property.name }

    val constructor = primaryConstructor ?: throw PrimaryConstructorMissing(this)
    val parameters = constructor.parameters
    val parametersToAnnotation = parameters.associate { param -> param to param.findAnnotation() }.filterNullValues()
    val propertyDescriptionsByKey = parametersToAnnotation.map { (param, annotation) ->
        val property = memberPropertiesByKey[annotation.key] ?: memberPropertiesByName[param.name]
        ?: throw ParameterPropertyNotFound(this, annotation, param)
        if (param.findAnnotation() != null) throw ConflictingAnnotations(this, param)
        if (param.findAnnotation() != null) throw ConflictingAnnotations(this, param)
        if (param.findAnnotation() != null) throw ConflictingAnnotations(this, param)
        if (annotation.key == idTag) throw ReservedIDName(this)
        if (annotation.key.contains(nestedPropertyDelimiter)) throw ReservedNestedPropertyDelimiter(this, annotation.key)
        if (annotation.key.toIntOrNull() != null) throw ReservedNumberKey(this, annotation.key)
        annotation.key to PropertyDescription(param, property, param.findMapper())
    }.associate { it }
    if (propertyDescriptionsByKey.size != parametersToAnnotation.size) throw DuplicatePropertyName(this)
    return propertyDescriptionsByKey
}

private fun KFunction<*>.nullParameters(): List =
        parameters.filter { param ->
            if (!param.isOptional &&
                    param.findAnnotation() == null &&
                    param.findAnnotation() == null &&
                    param.findAnnotation() == null &&
                    param.findAnnotation() == null) {
                if (!param.type.isMarkedNullable) throw NonNullableNonOptionalParameter(param)
                true
            } else {
                false
            }
        }

private fun verifyClassifiersAreCompatible(lowerBound: KClassifier?, upperBound: KClassifier?) {
    if (lowerBound == null || upperBound == null) {
        throw ClassifierUnavailable()
    }
    val lowerAsKClass = lowerBound as? KClass<*>
    val upperAsKClass = upperBound as? KClass<*>
    if (lowerAsKClass == null) {
        val lowerAsTypeParameter = lowerBound as KTypeParameter
        lowerAsTypeParameter.upperBounds.forEach {
            verifyClassifiersAreCompatible(it.classifier, upperBound)
        }
    } else if (upperAsKClass == null) {
        val upperAsTypeParameter = upperBound as KTypeParameter
        upperAsTypeParameter.upperBounds.forEach {
            verifyClassifiersAreCompatible(lowerBound, it.classifier)
        }
    } else if (!lowerAsKClass.isSubclassOf(upperBound)) {
        throw ClassInheritanceMismatch(lowerBound, upperBound)
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy