Skip to content

Commit

Permalink
[NU-1836] Handle extension methods in NoParamMethodPropertyAccessor
Browse files Browse the repository at this point in the history
  • Loading branch information
Łukasz Bigorajski committed Oct 18, 2024
1 parent 1359e16 commit 2fe17cf
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ object EvaluationContextPreparer {
classDefinitionSet: ClassDefinitionSet
): EvaluationContextPreparer = {
val conversionService = determineConversionService(expressionConfig)
val propertyAccessors = internal.propertyAccessors.configured()
val methodInvoker = new ExtensionsAwareMethodInvoker(new ExtensionMethodsInvoker(classLoader, classDefinitionSet))
val propertyAccessors = internal.propertyAccessors.configured(methodInvoker)
new EvaluationContextPreparer(
classLoader,
expressionConfig.globalImports,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ object propertyAccessors {
// Order of accessors matters - property from first accessor that returns `true` from `canRead` will be chosen.
// This general order can be overridden - each accessor can define target classes for which it will have precedence -
// through the `getSpecificTargetClasses` method.
def configured(): Seq[PropertyAccessor] = {
def configured(methodInvoker: ExtensionsAwareMethodInvoker): Seq[PropertyAccessor] = {
Seq(
MapPropertyAccessor, // must be before NoParamMethodPropertyAccessor and ReflectivePropertyAccessor
new ReflectivePropertyAccessor(),
Expand All @@ -25,7 +25,7 @@ object propertyAccessors {
PrimitiveOrWrappersPropertyAccessor,
StaticPropertyAccessor,
TypedDictInstancePropertyAccessor, // must be before NoParamMethodPropertyAccessor
NoParamMethodPropertyAccessor,
new NoParamMethodPropertyAccessor(methodInvoker),
// it can add performance overhead so it will be better to keep it on the bottom
MapLikePropertyAccessor,
MapMissingPropertyToNullAccessor, // must be after NoParamMethodPropertyAccessor
Expand All @@ -51,16 +51,22 @@ object propertyAccessors {
This one is a bit tricky. We extend ReflectivePropertyAccessor, as it's the only sensible way to make it compilable,
however it's not so easy to extend and in interpreted mode we skip original implementation
*/
object NoParamMethodPropertyAccessor extends ReflectivePropertyAccessor with ReadOnly with Caching {
class NoParamMethodPropertyAccessor(methodInvoker: ExtensionsAwareMethodInvoker)
extends ReflectivePropertyAccessor
with ReadOnly
with Caching {

private val methodsDiscovery = new ExtensionAwareMethodsDiscovery
private val emptyArray = Array[AnyRef]()

override def findGetterForProperty(propertyName: String, clazz: Class[_], mustBeStatic: Boolean): Method = {
findMethodFromClass(propertyName, clazz).orNull
}

override protected def reallyFindMethod(name: String, target: Class[_]): Option[Method] = {
target.getMethods.find(m =>
!ClassUtils.isPrimitiveOrWrapper(target) && m.getParameterCount == 0 && m.getName == name
)
methodsDiscovery
.discover(target)
.find(m => !ClassUtils.isPrimitiveOrWrapper(target) && m.getParameterCount == 0 && m.getName == name)
}

override protected def invokeMethod(
Expand All @@ -69,10 +75,27 @@ object propertyAccessors {
target: Any,
context: EvaluationContext
): AnyRef = {
method.invoke(target)
methodInvoker.invoke(method, target, emptyArray)
}

override def getSpecificTargetClasses: Array[Class[_]] = null

override def createOptimalAccessor(context: EvaluationContext, target: Any, name: String): PropertyAccessor =
new NuOptimalAccessor(super.createOptimalAccessor(context, target, name))

private class NuOptimalAccessor(delegate: PropertyAccessor) extends PropertyAccessor {
override def getSpecificTargetClasses: Array[Class[_]] =
delegate.getSpecificTargetClasses
override def canWrite(context: EvaluationContext, target: Any, name: String): Boolean =
delegate.canWrite(context, target, name)
override def write(context: EvaluationContext, target: Any, name: String, newValue: Any): Unit =
delegate.write(context, target, name, newValue)
override def canRead(context: EvaluationContext, target: Any, name: String): Boolean =
NoParamMethodPropertyAccessor.this.canRead(context, target, name)
override def read(context: EvaluationContext, target: Any, name: String): TypedValue =
NoParamMethodPropertyAccessor.this.read(context, target, name)
}

}

// Spring bytecode generation fails when we try to invoke methods on primitives, so we
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1510,7 +1510,7 @@ class SpelExpressionSpec extends AnyFunSuite with Matchers with ValidatedValuesD
}

test("should convert a Set to a List") {
val parsed = parse[Any](expr = "#setVal.toList()", context = ctx).validValue
val parsed = parse[Any](expr = "#setVal.toList", context = ctx).validValue
parsed.returnType.withoutValue shouldBe Typed.genericTypeClass[JList[_]](List(Typed.typedClass[String]))
parsed.expression.evaluateSync[Any](ctx) shouldBe List("a").asJava
}
Expand Down

0 comments on commit 2fe17cf

Please sign in to comment.