org.apache.tinkerpop.gremlin.ogm.reflection.ObjectDescription.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kotlin-gremlin-ogm Show documentation
Show all versions of kotlin-gremlin-ogm Show documentation
The Object Graph Mapping Library for Kotlin and Gremlin
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