-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Kotlin Evaluator implementation
- Loading branch information
Showing
14 changed files
with
397 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
24 changes: 24 additions & 0 deletions
24
src/main/kotlin/com/rapatao/projects/ruleset/engine/context/EvalEngine.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,29 @@ | ||
package com.rapatao.projects.ruleset.engine.context | ||
|
||
/** | ||
* The EvalEngine interface defines the contract for an evaluation engine that can execute | ||
* a block of code with given input data and return a result. | ||
* Implementations of this interface are responsible for providing the execution environment | ||
* and handling the invocation of the provided block of code. | ||
*/ | ||
interface EvalEngine { | ||
|
||
/** | ||
* Executes the provided block of code with the given input data and returns a boolean value indicating | ||
* the success or failure of the execution. | ||
* | ||
* @param inputData The input data to be used in the execution. | ||
* @param block A lambda function that takes in a context and a scope as parameters and returns a boolean value. | ||
* The context represents the context in which the execution takes place, and the scope represents | ||
* the scope of the execution. | ||
* @return The result of the execution. | ||
*/ | ||
fun <T> call(inputData: Any, block: EvalContext.() -> T): T | ||
|
||
/** | ||
* Returns the name of the evaluation engine. This can be used to identify the engine in logging or debugging. | ||
* | ||
* @return The name of the evaluation engine. | ||
*/ | ||
fun name(): String | ||
} |
64 changes: 64 additions & 0 deletions
64
src/main/kotlin/com/rapatao/projects/ruleset/engine/evaluator/kotlin/KotlinContext.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package com.rapatao.projects.ruleset.engine.evaluator.kotlin | ||
|
||
import com.rapatao.projects.ruleset.engine.context.EvalContext | ||
import com.rapatao.projects.ruleset.engine.evaluator.kotlin.Parser.parse | ||
import com.rapatao.projects.ruleset.engine.types.Expression | ||
import java.math.BigDecimal | ||
|
||
/** | ||
* KotlinContext is a class that implements the EvalContext interface. | ||
* It provides the ability to process expressions using Kotlin operations. | ||
* | ||
* @param inputData the map containing the input data to be used during expression evaluation | ||
*/ | ||
class KotlinContext( | ||
private val inputData: Map<String, Any?> | ||
) : EvalContext { | ||
|
||
override fun process(expression: Expression): Boolean { | ||
return parse( | ||
expression, | ||
expression.left.asValue(), | ||
expression.right.asValue() | ||
) | ||
} | ||
|
||
private fun Any?.asValue(): Any? { | ||
val result = when { | ||
this is String && !this.trim().matches(Regex("^\".*\"$")) -> rawValue() | ||
this is String && this.trim().matches(Regex("\".*\"")) -> this.unwrap() | ||
else -> this | ||
} | ||
|
||
return when { | ||
result is Number && (result is Double || result is Float) -> BigDecimal(result.toDouble()) | ||
result is Number && result !is Byte -> BigDecimal.valueOf(result.toLong()) | ||
else -> result | ||
} | ||
} | ||
|
||
private fun String.rawValue(): Any? { | ||
val key = this.unwrap() | ||
|
||
return listOf( | ||
{ | ||
key.toBigIntegerOrNull() | ||
}, | ||
{ | ||
key.toBigDecimalOrNull() | ||
}, | ||
{ | ||
key.toBooleanStrictOrNull() | ||
}, | ||
{ | ||
inputData.getOrElse(key) { | ||
throw NoSuchElementException("$key not found") | ||
} | ||
}, | ||
).firstNotNullOfOrNull { it() } | ||
} | ||
|
||
private fun String.unwrap() = this.trim() | ||
.replace(Regex("^\""), "") | ||
.replace(Regex("\"$"), "") | ||
} |
72 changes: 72 additions & 0 deletions
72
src/main/kotlin/com/rapatao/projects/ruleset/engine/evaluator/kotlin/KotlinEvalEngine.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package com.rapatao.projects.ruleset.engine.evaluator.kotlin | ||
|
||
import com.rapatao.projects.ruleset.engine.context.EvalContext | ||
import com.rapatao.projects.ruleset.engine.context.EvalEngine | ||
import kotlin.reflect.full.memberProperties | ||
|
||
/** | ||
* An evaluator engine implementation that uses Kotlin to process expressions. | ||
* | ||
* Supported types: Java primitive types, boolean, string, number types, maps, lists and arrays. | ||
*/ | ||
open class KotlinEvalEngine : EvalEngine { | ||
override fun <T> call(inputData: Any, block: (context: EvalContext) -> T): T { | ||
val params: MutableMap<String, Any?> = mutableMapOf() | ||
|
||
parseKeys("", params, inputData) | ||
|
||
return block(KotlinContext(params)) | ||
} | ||
|
||
override fun name(): String = "KotlinEval" | ||
|
||
private fun parseKeys(node: String, params: MutableMap<String, Any?>, input: Any?) { | ||
when { | ||
input.isValue() -> { | ||
params[node] = input | ||
} | ||
|
||
input is Collection<*> -> { | ||
params[node] = input | ||
|
||
input.forEachIndexed { index, value -> | ||
parseKeys("${node}[${index}]", params, value) | ||
} | ||
} | ||
|
||
input is Array<*> -> { | ||
parseKeys(node, params, input) | ||
} | ||
|
||
input is Map<*, *> -> { | ||
val currNode = if (node.isNotBlank()) { | ||
"${node}." | ||
} else { | ||
node | ||
} | ||
input.forEach { key, value -> | ||
parseKeys("${currNode}${key}", params, value) | ||
} | ||
} | ||
|
||
else -> { | ||
val currNode = if (node.isNotBlank()) { | ||
"${node}." | ||
} else { | ||
node | ||
} | ||
|
||
input?.javaClass?.kotlin?.memberProperties?.map { | ||
parseKeys("${currNode}${it.name}", params, it.get(input)) | ||
} | ||
} | ||
} | ||
} | ||
|
||
private fun Any?.isValue(): Boolean = | ||
this == null || | ||
this.javaClass.isPrimitive || | ||
this is Boolean || | ||
this is String || | ||
this is Number | ||
} |
32 changes: 32 additions & 0 deletions
32
src/main/kotlin/com/rapatao/projects/ruleset/engine/evaluator/kotlin/Parser.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package com.rapatao.projects.ruleset.engine.evaluator.kotlin | ||
|
||
import com.rapatao.projects.ruleset.engine.types.Expression | ||
import com.rapatao.projects.ruleset.engine.types.Operator | ||
|
||
internal object Parser { | ||
fun parse(expression: Expression, left: Any?, right: Any?) = | ||
when (expression.operator) { | ||
Operator.EQUALS -> left == right | ||
Operator.NOT_EQUALS -> left != right | ||
Operator.GREATER_THAN -> left.comparable() > right | ||
Operator.GREATER_OR_EQUAL_THAN -> left.comparable() >= right | ||
Operator.LESS_THAN -> left.comparable() < right | ||
Operator.LESS_OR_EQUAL_THAN -> left.comparable() <= right | ||
Operator.STARTS_WITH -> left.toString().startsWith(right.toString()) | ||
Operator.ENDS_WITH -> left.toString().endsWith(right.toString()) | ||
Operator.CONTAINS -> left.checkContains(right) | ||
null -> error("when evaluation an expression, the operator cannot be null") | ||
} | ||
|
||
@Suppress("UNCHECKED_CAST") | ||
private fun <T> T.comparable() = this as Comparable<T> | ||
|
||
private fun Any?.checkContains(value: Any?): Boolean { | ||
return when { | ||
this is String && value is String -> this.contains(value) | ||
this is Collection<*> -> this.contains(value) | ||
this is Array<*> -> this.contains(value) | ||
else -> throw UnsupportedOperationException("contains doesn't support ${this?.javaClass} type") | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.