I am hoping to get an explanation as to why the call to this Groovy method works as expected:
def f1(int n) {
return n + 1
}
println f1(1) // -> 2
But, if the parameter is not specifically defined ("def n" instead of "int n"), the method call needs to change:
def f2(def n) {
return n + 1
}
println f2(1) // Exception: Illegal class name
println this.&f2(1) // -> 2
What is happening under the hood to make this necessary?
UPDATED with more info:
This is on Windows with Groovy 2.4.5 JVM 1.8.0_51
The entire script is those 9 lines in a file called 1.groovy - nothing else.
I am running this from the console (cmdr) using "groovy 1.groovy"
The error on line 8 is:
Caught: java.lang.ClassFormatError: Illegal class name "3$f2" in class file 3$f2
java.lang.ClassFormatError: Illegal class name "3$f2" in class file 3$f2
at 3.run(3.groovy:8)
This is related to the name of your Script. When you have a file "1.groovy", Groovy generate a class with the name "1" in the default package, which is not a valid class name.
When you use f2(n) without a type, as this method is "too generic", Groovy try to find a matching method, or a class named f2, or an inner class named f2 : loading an inner class f2 of the class 1 fail, because the name is invalid.
Related
I want to use some method of a class, especified by a parameter. So I have defined a map of pointers with a diccionary. The problem is that Python doesn't let me define it outside another method (for example the constructor). So I wonder if it is posible. Let me show you an example:
class clase():
mapa = { 1 : metodo1,
2 : metodo2 }
def metodo(self,x):
self.mapa[x]()
def metodo1(self):
print("Metodo 1 ejecutado")
def metodo2(self):
print("Metodo 2 ejecutado")
objeto = clase()
objeto.metodo(2)
objeto.metodo(1)
Here the error says that "name 'metodo1' is not defined"; and If I use this other declaration:
class clase():
mapa = { 1 : self.metodo1,
2 : self.metodo2 }
...
The error is "name 'self' is not defined" (and that surprised me so much!)
How I said, I could do it defining the map inside the constructor. It works. But I want to know if there is any way to do it outside any method because I think I would be more readable during the maintainnig of the code.
ACTUALIZATION:
I proved declaring the map at the end:
class clase():
def metodo(self,x):
self.mapa[x]()
def metodo1(self):
print("Metodo 1 ejecutado")
def metodo2(self):
print("Metodo 2 ejecutado")
mapa = { 1 : metodo1,
2 : metodo2 }
objeto = clase()
objeto.metodo(2)
But I got another error: "metodo2() missing 1 required positional argument: 'self'"
I have groovy code as below:
def randomInt = RandomUtil.getRandomInt(1,200);
log.info randomInt
def chars = (("1".."9") + ("A".."Z") + ("a".."z")).join()
def randomString = RandomUtil.getRandomString(chars, randomInt) //works well with this code
log.info randomString
evaluate("log.info new Date()")
evaluate('RandomUtil.getRandomString(chars, randomInt)') //got error with this code
I want to evaluate a String which one like a {classname}.{methodname} in SoapUI with Groovy, just like above, but got error here, how to handle this and make it works well as I expect?
I have tried as blew:
evaluate('RandomUtil.getRandomString(chars, randomInt)') //got error with this code
Error As below:
Thu May 23 22:26:30 CST 2019:ERROR:An error occurred [No such property: getRandomString(chars, randomInt) for class: com.hypers.test.apitest.util.RandomUtil], see error log for details
The following code:
log = [info: { println(it) }]
class RandomUtil {
static def random = new Random()
static int getRandomInt(int from, int to) {
from + random.nextInt(to - from)
}
static String getRandomString(alphabet, len) {
def s = alphabet.size()
(1..len).collect { alphabet[random.nextInt(s)] }.join()
}
}
randomInt = RandomUtil.getRandomInt(1, 200)
log.info randomInt
chars = ('a'..'z') + ('A'..'Z') + ('0'..'9')
def randomString = RandomUtil.getRandomString(chars, 10) //works well with this code
log.info randomString
evaluate("log.info new Date()")
evaluate('RandomUtil.getRandomString(chars, randomInt)') //got error with this code
emulates your code, works, and produces the following output when run:
~> groovy solution.groovy
70
DDSQi27PYG
Thu May 23 20:51:58 CEST 2019
~>
I made up the RandomUtil class as you did not include the code for it.
I think the reason you are seeing the error you are seeing is that you define your variables char and randomInt using:
def chars = ...
and
def randomInt = ...
this puts the variables in local script scope. Please see this stackoverflow answer for an explanation with links to documentation of different ways of putting things in the script global scope and an explanation of how this works.
Essentially your groovy script code is implicitly an instance of the groovy Script class which in turn has an implicit Binding instance associated with it. When you write def x = ..., your variable is locally scoped, when you write x = ... or binding.x = ... the variable is defined in the script binding.
The evaluate method uses the same binding as the implicit script object. So the reason my example above works is that I omitted the def and just typed chars = and randomInt = which puts the variables in the script binding thus making them available for the code in the evaluate expression.
Though I have to say that even with all that, the phrasing No such property: getRandomString(chars, randomInt) seems really strange to me...I would have expected No such method or No such property: chars etc.
Sharing the code for for RandomUtil might help here.
Given is a class EnumTest that declares an inner enum MyEnum.
Using MyEnum as parameter type from within the class works as expected.
Using MyEnum as a parameter type outside of EnumTest fails to compile with unable to resolve class test.EnumTest.MyEnum.
I've browsed related questions, of which the best one was this, but they didn't address the specific issue of using the enum as a type.
Am I missing something very obvious here (as I'm very new to Groovy)? Or is this just another of the language's quirks "enhancements" regarding enums?
Edit: This is just a test demonstrating the issue. The actual issue happens in Jenkins JobDSL, and classpaths and imports seem to be fine there otherwise.
Groovy Version: 2.4.8
JVM: 1.8.0_201
Vendor: Oracle Corporation
OS: Linux
$ tree test
test
├── EnumTest.groovy
├── File2.groovy
└── File3.groovy
EnumTest.groovy:
package test
public class EnumTest {
public static enum MyEnum {
FOO, BAR
}
def doStuff(MyEnum v) {
println v
}
}
File2.groovy:
package test
import test.EnumTest
// prints BAR
new EnumTest().doStuff(EnumTest.MyEnum.BAR)
// prints FOO
println EnumTest.MyEnum.FOO
File3.groovy:
package test
import test.EnumTest
// fails: unable to resolve class test.EnumTest.MyEnum
def thisShouldWorkIMHO(EnumTest.MyEnum v) {
println v
}
When I'm running the test files using groovy -cp %, the output is as follows:
# groovy -cp . File2.groovy
BAR
FOO
# groovy -cp . File3.groovy
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
/home/lwille/-/test/GroovyTest2.groovy: 6: unable to resolve class EnumTest.MyEnum
# line 6, column 24.
def thisShouldWorkIMHO(EnumTest.MyEnum v) {
^
1 error
A few things worth mentioning. You don't need to import classes from the same package. Secondly, when you use a package test then you need to execute Groovy from the root folder, e.g. groovy test/File3.groovy to properly set up the classpath. (There is no need to use -cp . in such case).
Here's what it should look like.
$ tree test
test
├── EnumTest.groovy
├── File2.groovy
└── File3.groovy
0 directories, 3 files
test/EnumTest.groovy
package test
public class EnumTest {
public static enum MyEnum {
FOO, BAR
}
def doStuff(MyEnum v) {
println v
}
}
test/File2.groovy
package test
// prints BAR
new EnumTest().doStuff(EnumTest.MyEnum.BAR)
// prints FOO
println EnumTest.MyEnum.FOO
test/File3.groovy
package test
// fails: unable to resolve class test.EnumTest.MyEnum
def thisShouldWorkIMHO(EnumTest.MyEnum v) {
println v
}
thisShouldWorkIMHO(EnumTest.MyEnum.BAR)
The console output:
$ groovy test/File2.groovy
BAR
FOO
$ groovy test/File3.groovy
BAR
However, if you want to execute script from inside the test folder then you need to specify the classpath to point to the parent folder, e.g.:
$ groovy -cp ../. File3.groovy
BAR
$ groovy -cp . File3.groovy
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
/home/wololock/workspace/groovy-sandbox/src/test/File3.groovy: 4: unable to resolve class EnumTest.MyEnum
# line 4, column 24.
def thisShouldWorkIMHO(EnumTest.MyEnum v) {
^
1 error
UPDATE: the difference between Groovy 2.4 and 2.5 versions
One thing worth mentioning - the above solution works for Groovy 2.5.x and above. It is important to understand that things like methods parameters type check happen at the compiler's Phase.SEMANTIC_ANALYSIS phase. In Groovy 2.4 version, semantic analysis class resolving happens without loading classes. In case of using an inner class, it is critical to load its outer class so it can get resolved. Groovy 2.5 fixed that problem (intentionally or not) and semantic analysis resolves inner classes without an issue mentioned in this question.
For more detailed analysis, please check the following Stack Overflow question GroovyScriptEngine throws MultipleCompilationErrorsException while loading class that uses other class' static inner class where I have investigated a similar issue found in a Groovy 2.4 script. I explained there step by step how to dig down to the roots of this problem.
I'm trying to create a field mapping to map fields from user-friendly names to member variables in a variety of domain objects. The larger context is that I'm building up an ElasticSearch query based on user-constructed rules stored in a database, but for the sake of MCVE:
class MyClass {
Integer amount = 123
}
target = new MyClass()
println "${target.amount}"
fieldMapping = [
'TUITION' : 'target.amount'
]
fieldName = 'TUITION'
valueSource = '${' + "${fieldMapping[fieldName]}" + '}'
println valueSource
value = Eval.me('valueSource')
The Eval fails. Here's the output:
123
${target.amount}
Caught: groovy.lang.MissingPropertyException: No such property: valueSource for class: Script1
groovy.lang.MissingPropertyException: No such property: valueSource for class: Script1
at Script1.run(Script1.groovy:1)
at t.run(t.groovy:17)
What's necessary to evaluate the generated variable name and return the value 123? It seems like the real problem is that it's not recognizing that valueSource has been defined, not the actual expression held in valueSource, but that could be wring, too.
You're almost there, but you need to use a slightly different mechanism: the GroovyShell. You can instantiate a GroovyShell and use it to evaluate a String as a script, returning the result. Here's your example, modified to work properly:
class MyClass {
Integer amount = 123
}
target = new MyClass()
fieldMapping = [
'TUITION' : 'target.amount'
]
fieldName = 'TUITION'
// These are the values made available to the script through the Binding
args = [target: target]
// Create the shell with the binding as a parameter
shell = new GroovyShell(args as Binding)
// Evaluate the "script", which in this case is just the string "target.amount".
// Inside the shell, "target" is available because you added it to the shell's binding.
result = shell.evaluate(fieldMapping[fieldName])
assert result == 123
assert result instanceof Integer
Using Scala Scripting Engine in 2.11 Milestone 7, how do I get a typed value back from the script engine? I'm getting error messages like "mypackage.Holler cannot be cast to mypackage.Holler".
Here is the use case (reduced to essentials). I want to use scripts to prepare and configure objects of a standard type that I will process in my main program. I have a trait:
package mypackage
trait Holler {
def shout: Unit
}
I have a user script in Scala, saved in the file /home/me/Foo.scala
object Foo extends mypackage.Holler {
def shout: Unit = println("Hello World!")
}
When I run this script using IMain.eval(Reader), I expect that the object Foo will be returned since it is the result of the last statement. Here is a program, including a couple useful printouts to run the script:
package mypackage
import javax.script.ScriptEngineManager
import scala.tools.nsc.interpreter.IMain
object Runner {
def main(args: Array[String]): Unit = {
// Create the script engine
val javaxEngine = new ScriptEngineManager().getEngineByName("scala")
val scalaEngine = javaEngine.asInstanceOf[IMain]
// Configure script engine to use the Java classpath
val useJavaClassPath = scalaEngine.settings.usejavacp.tryToSet(List("true"))
println("Use Java CP? " + useJavaClassPath)
val script = new java.io.FileReader("/home/me/Foo.scala")
val result = scalaEngine.eval(script)
println("Script Result Type: " + result.getClass.getName)
println("Defined Symbols: " + scalaEngine.definedSymbolList)
val myHoller = result.asInstanceOf[mypackage.Holler]
}
}
The script runs just fine under the script engine. But the result cannot be cast to Holler. The output of this program is as follows:
Use Java CP? Some(List(true))
Script Result Type: $line3.$read$$iw$$iw$Foo$
Defined Symbols: List(value engine, object iw$Foo)
Exception in thread "main" java.lang.ClassCastException: $line3.$read$$iw$$iw$Foo$ cannot be cast to mypackage.Holler
This tells me that the classpath is successfully recognized by the script engine, and that the Foo object is being constructed. But the trait mypackage.Holler (from the common classpath) inside the script is different from the trait mypackage.Holler in the main program.
If I add the following lines to the script:
Foo.shout
val result: Holler = Foo
I see:
The shout method being exercised ("Hello World!" prints out),
"result" is added to the list of defined symbols
result is clearly compatible with type Holler.
I can bind a "catcher" object to the script engine. Its code looks like this:
package mypackage
class Catcher {
var item: Holler = null
}
And I bind with
val mycatcher = new Catcher
scalaEngine.bind("catcher", mycatcher)
scalaEngine.eval("catcher = Foo")
Now "catcher" shows up in the list of defined symbols to the script engine and I can use the catcher to go into the script engine with a command like
scalaScriptEngine.eval("catcher.item = result")
But then I get strange "compile time" ClassCastExceptions saying:
mypackage.Holler cannot be cast to mypackage.Holler
If I make the "item" in the Catcher an Any, then I don't get the exception until I do
mycatcher.item.asInstanceOf[Holler]
in the main program. But I still get pretty much the same exception. It is as if two incompatible class loaders are being used with the same classpath. So how, from the main program, do I access the Foo object as an instance of Holler (which it clearly implements in the script engine)?