• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

TouK / nussknacker / 5976637142

25 Aug 2023 01:43PM UTC coverage: 81.47% (+0.03%) from 81.438%
5976637142

push

github

Filemon279
Fix migration in 1.11

25 of 25 new or added lines in 2 files covered. (100.0%)

14865 of 18246 relevant lines covered (81.47%)

5.62 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.69
/interpreter/src/main/scala/pl/touk/nussknacker/engine/spel/internal/propertyAccessors.scala
1
package pl.touk.nussknacker.engine.spel.internal
2

3
import java.lang.reflect.{Method, Modifier}
4
import java.util.Optional
5
import org.apache.commons.lang3.ClassUtils
6
import org.springframework.expression.spel.support.ReflectivePropertyAccessor
7
import org.springframework.expression.{EvaluationContext, PropertyAccessor, TypedValue}
8
import pl.touk.nussknacker.engine.api.dict.DictInstance
9
import pl.touk.nussknacker.engine.api.exception.NonTransientException
10
import pl.touk.nussknacker.engine.api.typed.TypedMap
11

12
import scala.collection.concurrent.TrieMap
13
import scala.concurrent.duration._
14

15
object propertyAccessors {
16

17
  def configured(): Seq[PropertyAccessor] = {
18

19
    Seq(
36✔
20
      new ReflectivePropertyAccessor(),
36✔
21
      NullPropertyAccessor, //must come before other non-standard ones
36✔
22
      ScalaOptionOrNullPropertyAccessor, // must be before scalaPropertyAccessor
36✔
23
      JavaOptionalOrNullPropertyAccessor,
36✔
24
      NoParamMethodPropertyAccessor,
36✔
25
      PrimitiveOrWrappersPropertyAccessor,
36✔
26
      StaticPropertyAccessor,
36✔
27
      MapPropertyAccessor,
36✔
28
      TypedDictInstancePropertyAccessor,
36✔
29
      // it can add performance overhead so it will be better to keep it on the bottom
30
      MapLikePropertyAccessor
36✔
31
    )
32
  }
33

34
  object NullPropertyAccessor extends PropertyAccessor with ReadOnly {
35

36
    override def getSpecificTargetClasses: Array[Class[_]] = null
18✔
37

38
    override def canRead(context: EvaluationContext, target: Any, name: String): Boolean = target == null
16✔
39

40
    override def read(context: EvaluationContext, target: Any, name: String): TypedValue =
41
      //can we extract anything else here?
42
      throw NonTransientException(name, s"Cannot invoke method/property $name on null object")
×
43
  }
44

45
  /* PropertyAccessor for methods without parameters - e.g. parameters in case classes
46
    TODO: is it ok to treat all methods without parameters as properties?
47
    We have to handle Primitives/Wrappers differently, as they have problems with bytecode generation (@see PrimitiveOrWrappersPropertyAccessor)
48

49
    This one is a bit tricky. We extend ReflectivePropertyAccessor, as it's the only sensible way to make it compilable,
50
    however it's not so easy to extend and in interpreted mode we skip original implementation
51
   */
52
  object NoParamMethodPropertyAccessor extends ReflectivePropertyAccessor with ReadOnly with Caching {
53

54
    override def findGetterForProperty(propertyName: String, clazz: Class[_], mustBeStatic: Boolean): Method = {
55
      findMethodFromClass(propertyName, clazz).orNull
14✔
56
    }
57

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

62
    override protected def invokeMethod(propertyName: String, method: Method, target: Any, context: EvaluationContext): AnyRef = {
63
      method.invoke(target)
×
64
    }
65

66
    override def getSpecificTargetClasses: Array[Class[_]] = null
18✔
67
  }
68

69
  //Spring bytecode generation fails when we try to invoke methods on primitives, so we
70
  //*do not* extend ReflectivePropertyAccessor and we force interpreted mode
71
  //TODO: figure out how to make bytecode generation work also in this case
72
  object PrimitiveOrWrappersPropertyAccessor extends PropertyAccessor with ReadOnly with Caching {
73

74
    override def getSpecificTargetClasses: Array[Class[_]] = null
18✔
75

76
    override protected def invokeMethod(propertyName: String, method: Method, target: Any, context: EvaluationContext): Any
77
      = method.invoke(target)
2✔
78

79
    override protected def reallyFindMethod(name: String, target: Class[_]): Option[Method] = {
80
      target.getMethods.find(m => ClassUtils.isPrimitiveOrWrapper(target) && m.getParameterCount == 0 && m.getName == name)
14✔
81
    }
82
  }
83

84
  object StaticPropertyAccessor extends PropertyAccessor with ReadOnly with StaticMethodCaching {
85

86
    override protected def reallyFindMethod(name: String, target: Class[_]): Option[Method] = {
87
      target.asInstanceOf[Class[_]].getMethods.find(m =>
×
88
        m.getParameterCount == 0 && m.getName == name && Modifier.isStatic(m.getModifiers)
×
89
      )
90
    }
91

92
    override protected def invokeMethod(propertyName: String, method: Method, target: Any, context: EvaluationContext): Any = {
93
      method.invoke(target)
×
94
    }
95

96
    override def getSpecificTargetClasses: Array[Class[_]] = null
18✔
97
  }
98

99
  // TODO: handle methods with multiple args or at least validate that they can't be called
100
  //       - see test for similar case for Futures: "usage of methods with some argument returning future"
101
  object ScalaOptionOrNullPropertyAccessor extends PropertyAccessor with ReadOnly with Caching {
102

103
    override protected def reallyFindMethod(name: String, target: Class[_]) : Option[Method] = {
104
      target.getMethods.find(m => m.getParameterCount == 0 && m.getName == name && classOf[Option[_]].isAssignableFrom(m.getReturnType))
16✔
105
    }
106

107
    override protected def invokeMethod(propertyName: String, method: Method, target: Any, context: EvaluationContext): Any = {
108
      method.invoke(target).asInstanceOf[Option[Any]].orNull
2✔
109
    }
110

111
    override def getSpecificTargetClasses: Array[Class[_]] = null
18✔
112
  }
113

114
  // TODO: handle methods with multiple args or at least validate that they can't be called
115
  //       - see test for similar case for Futures: "usage of methods with some argument returning future"
116
  object JavaOptionalOrNullPropertyAccessor extends PropertyAccessor with ReadOnly with Caching {
117

118
    override protected def reallyFindMethod(name: String, target: Class[_]) : Option[Method] = {
119
      target.getMethods.find(m => m.getParameterCount == 0 && m.getName == name && classOf[Optional[_]].isAssignableFrom(m.getReturnType))
14✔
120
    }
121

122
    override protected def invokeMethod(propertyName: String, method: Method, target: Any, context: EvaluationContext): Any = {
123
      method.invoke(target).asInstanceOf[Optional[Any]].orElse(null)
2✔
124
    }
125

126
    override def getSpecificTargetClasses: Array[Class[_]] = null
18✔
127
  }
128

129
  object MapPropertyAccessor extends PropertyAccessor with ReadOnly {
130

131
    override def canRead(context: EvaluationContext, target: scala.Any, name: String): Boolean =
132
      true
14✔
133

134
    override def read(context: EvaluationContext, target: scala.Any, name: String) =
135
      new TypedValue(target.asInstanceOf[java.util.Map[_, _]].get(name))
14✔
136

137
    override def getSpecificTargetClasses: Array[Class[_]] = Array(classOf[java.util.Map[_, _]])
18✔
138
  }
139

140
  object TypedDictInstancePropertyAccessor extends PropertyAccessor with ReadOnly {
141
    //in theory this always happends, because we typed it properly ;)
142
    override def canRead(context: EvaluationContext, target: scala.Any, key: String) =
143
      true
4✔
144

145
    // we already replaced dict's label with keys so we can just return value based on key
146
    override def read(context: EvaluationContext, target: scala.Any, key: String) =
147
      new TypedValue(target.asInstanceOf[DictInstance].value(key))
4✔
148

149
    override def getSpecificTargetClasses: Array[Class[_]] = Array(classOf[DictInstance])
18✔
150
  }
151

152
  // mainly for avro's GenericRecord purpose
153
  object MapLikePropertyAccessor extends PropertyAccessor with Caching with ReadOnly {
154

155
    override protected def invokeMethod(propertyName: String, method: Method, target: Any, context: EvaluationContext): Any = {
156
      method.invoke(target, propertyName)
6✔
157
    }
158

159
    override protected def reallyFindMethod(name: String, target: Class[_]): Option[Method] = {
160
      target.getMethods.find(m => m.getName == "get" && (m.getParameterTypes sameElements Array(classOf[String])))
6✔
161
    }
162

163
    override def getSpecificTargetClasses: Array[Class[_]] = null
18✔
164
  }
165

166
  trait Caching extends CachingBase { self: PropertyAccessor =>
167

168
    override def canRead(context: EvaluationContext, target: scala.Any, name: String): Boolean =
169
      !target.isInstanceOf[Class[_]] && findMethod(name, target).isDefined
16✔
170

171
    override protected def extractClassFromTarget(target: Any): Option[Class[_]] =
172
      Option(target).map(_.getClass)
16✔
173
  }
174

175
  trait StaticMethodCaching extends CachingBase { self: PropertyAccessor =>
176
    override def canRead(context: EvaluationContext, target: scala.Any, name: String): Boolean =
177
      target.isInstanceOf[Class[_]] && findMethod(name, target).isDefined
7✔
178

179
    override protected def extractClassFromTarget(target: Any): Option[Class[_]] = Option(target).map(_.asInstanceOf[Class[_]])
×
180
  }
181

182
  trait CachingBase { self: PropertyAccessor =>
183
    private val methodsCache = new TrieMap[(String, Class[_]), Option[Method]]()
36✔
184

185
    override def read(context: EvaluationContext, target: scala.Any, name: String): TypedValue =
186
      findMethod(name, target)
187
        .map { method =>
188
          new TypedValue(invokeMethod(name, method, target, context))
6✔
189
        }
190
        .getOrElse(throw new IllegalAccessException("Property is not readable"))
×
191

192
    protected def findMethod(name: String, target: Any): Option[Method] = {
193
      //this should *not* happen as we have NullPropertyAccessor
194
      val targetClass = extractClassFromTarget(target).getOrElse(throw new IllegalArgumentException(s"Null target for $name"))
16✔
195
      findMethodFromClass(name, targetClass)
16✔
196
    }
197

198
    protected def findMethodFromClass(name: String, targetClass: Class[_]): Option[Method] = {
199
      methodsCache.getOrElseUpdate((name, targetClass), reallyFindMethod(name, targetClass))
16✔
200
    }
201

202

203
    protected def extractClassFromTarget(target: Any): Option[Class[_]]
204
    protected def invokeMethod(propertyName: String, method: Method, target: Any, context: EvaluationContext): Any
205
    protected def reallyFindMethod(name: String, target: Class[_]) : Option[Method]
206
  }
207

208
  trait ReadOnly { self: PropertyAccessor =>
209

210
    override def write(context: EvaluationContext, target: scala.Any, name: String, newValue: scala.Any) =
211
      throw new IllegalAccessException("Property is not writeable")
×
212

213
    override def canWrite(context: EvaluationContext, target: scala.Any, name: String) = false
×
214

215
  }
216

217
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc