com.zepben.evolve.services.common.BaseServiceComparator.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of evolve-sdk Show documentation
Show all versions of evolve-sdk Show documentation
SDK for interaction with the evolve platform
/*
* Copyright 2020 Zeppelin Bend Pty Ltd
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.zepben.evolve.services.common
import com.zepben.evolve.cim.iec61968.common.Document
import com.zepben.evolve.cim.iec61968.common.Organisation
import com.zepben.evolve.cim.iec61968.common.OrganisationRole
import com.zepben.evolve.cim.iec61970.base.core.IdentifiedObject
import com.zepben.evolve.cim.iec61970.base.core.Name
import com.zepben.evolve.cim.iec61970.base.core.NameType
import java.security.AccessController
import java.security.PrivilegedActionException
import java.security.PrivilegedExceptionAction
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KProperty1
import kotlin.reflect.KType
import kotlin.reflect.full.createType
import kotlin.reflect.full.memberFunctions
import kotlin.reflect.full.superclasses
import kotlin.reflect.jvm.isAccessible
abstract class BaseServiceComparator {
@Suppress("UNCHECKED_CAST")
private val compareByType: Map>> = this::class.memberFunctions
.asSequence()
.filter { it.name.startsWith("compare") }
.filter { it.parameters.size == 3 }
.filter { it.parameters[1].type == it.parameters[2].type }
.filter { it.parameters[1].name == "source" }
.filter { it.parameters[2].name == "target" }
.filter { it.returnType.classifier == ObjectDifference::class }
.filter {
try {
AccessController.doPrivileged(PrivilegedExceptionAction {
it.isAccessible = true
true
} as PrivilegedExceptionAction)
} catch (e: PrivilegedActionException) {
false
}
}
.associateBy({ it.parameters[1].type }, { it as KFunction> })
/**
* Run the compare with the specified optional checks
*
* @param source The network service to use as the source
* @param target The network service to use as the target
* @return The differences detected between the source and the target
*/
fun compare(
source: BaseService,
target: BaseService
): ServiceDifferences {
val differences = ServiceDifferences({ source[it] }, { target[it] }, { source.getNameType(it) }, { target.getNameType(it) })
source.sequenceOf().forEach { s ->
val difference = target.get(s.mRID)?.let { t ->
val sourceType = getComparableType(s::class)
if (sourceType != getComparableType(t::class)) {
differences.addToMissingFromSource(s.mRID)
null
} else {
requireNotNull(compareByType[sourceType]) {
"INTERNAL ERROR: Attempted to compare Zepben CIM class ${s::class} which is not registered with the comparator."
}.call(this, s, t)
}
}
if (difference == null) // Wasn't present in target
differences.addToMissingFromTarget(s.mRID)
else if (difference.differences.isNotEmpty()) // present, but not the same
differences.addModifications(s.mRID, difference)
}
target.sequenceOf()
.filter { !source.contains(it.mRID) }
.forEach { differences.addToMissingFromSource(it.mRID) }
source.nameTypes.forEach { s ->
val difference = target.getNameType(s.name)?.let { t -> compareNameType(s, t) }
if (difference == null) // Wasn't present in target
differences.addToMissingFromTarget(s.name)
else if (difference.differences.isNotEmpty()) // present, but not the same
differences.addModifications(s.name, difference)
}
target.nameTypes
.filter { source.getNameType(it.name) == null }
.forEach { differences.addToMissingFromSource(it.name) }
return differences
}
@Suppress("UNCHECKED_CAST")
fun compare(source: T, target: T): ObjectDifference {
val sourceType = getComparableType(source::class)
val targetType = getComparableType(target::class)
require(sourceType == targetType) { "source and target must be of the same type" }
return requireNotNull(compareByType[sourceType]) {
"INTERNAL ERROR: Attempted to compare Zepben CIM class ${source::class} which is not registered with the comparator."
}.call(this, source, target) as ObjectDifference
}
protected fun ObjectDifference.compareIdentifiedObject(): ObjectDifference = apply {
compareValues(IdentifiedObject::mRID, IdentifiedObject::name, IdentifiedObject::description, IdentifiedObject::numDiagramObjects)
compareNames(IdentifiedObject::names)
}
private fun compareNameType(source: NameType, target: NameType): ObjectDifference =
ObjectDifference(source, target).apply {
compareValues(NameType::description)
fun Name.compareMatch(other: Name): Boolean =
this.name == other.name &&
this.type.name == other.type.name &&
this.identifiedObject.mRID == other.identifiedObject.mRID
val differences = CollectionDifference()
source.names.forEach { sName ->
if (!target.names.any { tName -> sName.compareMatch(tName) })
differences.missingFromTarget.add(sName)
}
target.names.forEach { tName ->
if (!source.names.any { sName -> tName.compareMatch(sName) })
differences.missingFromSource.add(tName)
}
addIfDifferent(NameType::names.name, differences.nullIfEmpty())
}
protected fun ObjectDifference.compareDocument(): ObjectDifference =
apply {
compareIdentifiedObject()
compareValues(Document::title, Document::createdDateTime, Document::authorName, Document::type, Document::status, Document::comment)
}
protected fun ObjectDifference.compareOrganisationRole(): ObjectDifference =
apply {
compareIdentifiedObject()
compareIdReferences(OrganisationRole::organisation)
}
// Used via reflection
@Suppress("Unused")
protected fun compareOrganisation(source: Organisation, target: Organisation): ObjectDifference =
ObjectDifference(source, target).apply {
compareIdentifiedObject()
}
private fun getComparableType(clazz: KClass<*>): KType? {
val packageName = if (clazz.qualifiedName.isNullOrBlank()) null else clazz.java.packageName
if (packageName?.startsWith("com.zepben.evolve.cim.") == true)
return clazz.createType()
return clazz.superclasses
.asSequence()
.map { getComparableType(it) }
.filterNotNull()
.firstOrNull()
}
fun ObjectDifference.compareValues(
vararg properties: KProperty1
): ObjectDifference {
properties.forEach { addIfDifferent(it.name, it.compareValues(source, target)) }
return this
}
fun ObjectDifference.compareIdReferences(
vararg properties: KProperty1
): ObjectDifference {
properties.forEach { addIfDifferent(it.name, it.compareIdReference(source, target)) }
return this
}
private fun ObjectDifference.compareNames(
vararg properties: KProperty1>
): ObjectDifference {
properties.forEach { addIfDifferent(it.name, it.compareNames(source, target)) }
return this
}
fun ObjectDifference.compareIdReferenceCollections(
vararg properties: KProperty1>
): ObjectDifference {
properties.forEach { addIfDifferent(it.name, it.compareIdReferenceCollection(source, target)) }
return this
}
fun ObjectDifference.compareIndexedIdReferenceCollections(
vararg properties: KProperty1>
): ObjectDifference {
properties.forEach { addIfDifferent(it.name, it.compareIndexedIdReferenceCollection(source, target)) }
return this
}
fun ObjectDifference.compareIndexedValueCollections(
vararg properties: KProperty1>
): ObjectDifference {
properties.forEach { addIfDifferent(it.name, it.compareIndexedValueCollection(source, target)) }
return this
}
fun ObjectDifference.addIfDifferent(name: String, difference: Difference?) {
if (difference != null) {
differences[name] = difference
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy