GroovyScriptEngine throws MultipleCompilationErrorsException while loading class that uses other class' static inner class - groovy

I'm running into a problem with GroovyScriptEngine - it seems not to be able to work with inner classes. Anyone know whether there's some limitation in GroovyScriptEngine or a workaround?
I have a directory with these two files:
// MyClass.groovy
public class MyClass {
MyOuter m1;
MyOuter.MyInner m2;
}
and
// MyOuter.groovy
public class MyOuter {
public static class MyInner {}
}
I have a following test class:
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import groovy.util.GroovyScriptEngine;
public class TestGroovyScriptEngine {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException {
final File myGroovySourceDir = new File("C:/MyGroovySourceDir");
final URL[] urls = { myGroovySourceDir.toURL() };
GroovyScriptEngine groovyScriptEngine = new GroovyScriptEngine(urls,
Thread.currentThread().getContextClassLoader());
Class<?> clazz = groovyScriptEngine.getGroovyClassLoader().loadClass("MyClass");
}
}
When I run it I get the following compilation error:
Exception in thread "main" org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
C:\MyGroovySourceDir\MyClass.groovy: 3: unable to resolve class MyOuter.MyInner
# line 3, column 2.
MyOuter.MyInner m2;
^
1 error
at org.codehaus.groovy.control.ErrorCollector.failIfErrors(ErrorCollector.java:311)
at org.codehaus.groovy.control.CompilationUnit.applyToSourceUnits(CompilationUnit.java:983)
at org.codehaus.groovy.control.CompilationUnit.doPhaseOperation(CompilationUnit.java:633)
at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:582)
at groovy.lang.GroovyClassLoader.doParseClass(GroovyClassLoader.java:354)
at groovy.lang.GroovyClassLoader.access$300(GroovyClassLoader.java:87)
at groovy.lang.GroovyClassLoader$5.provide(GroovyClassLoader.java:323)
at groovy.lang.GroovyClassLoader$5.provide(GroovyClassLoader.java:320)
at org.codehaus.groovy.runtime.memoize.ConcurrentCommonCache.getAndPut(ConcurrentCommonCache.java:147)
at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:318)
at groovy.util.GroovyScriptEngine$ScriptClassLoader.doParseClass(GroovyScriptEngine.java:248)
at groovy.util.GroovyScriptEngine$ScriptClassLoader.parseClass(GroovyScriptEngine.java:235)
at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:307)
at groovy.lang.GroovyClassLoader.recompile(GroovyClassLoader.java:811)
at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:767)
at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:836)
at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:824)
I would have expected a "clean compile", but the inner class seems to be causing problems.
My groovy classes compile fine at the command line using groovyc, or in Eclipse.

You have faced an edge case here. To clarify what happens let's define the initial conditions:
you have a Java (or Groovy) class that gets executed inside JVM
you have two Groovy classes that get loaded outside of the JVM
The problem you have described does not exist if you put these two Groovy classes inside the same path you execute your Java class from - in this case IDE takes care to compile these Groovy classes and put them to the classpath of a JVM that gets started to run your Java test class.
But this is not your case and you are trying to load these two Groovy classes outside the running JVM using GroovyClassLoader (which extends URLClassLoader btw). I will try to explain in the simplest possible words what happened that adding field of type MyOuter does not throw any compilation error, but MyOuter.MyInner does.
When you execute:
Class<?> clazz = groovyScriptEngine.getGroovyClassLoader().loadClass("MyClass");
Groovy class loader goes to script file lookup part, because it was not able to find MyClass in the current classpath. This is the part responsible for it:
// at this point the loading from a parent loader failed
// and we want to recompile if needed.
if (lookupScriptFiles) {
// try groovy file
try {
// check if recompilation already happened.
final Class classCacheEntry = getClassCacheEntry(name);
if (classCacheEntry != cls) return classCacheEntry;
URL source = resourceLoader.loadGroovySource(name);
// if recompilation fails, we want cls==null
Class oldClass = cls;
cls = null;
cls = recompile(source, name, oldClass);
} catch (IOException ioe) {
last = new ClassNotFoundException("IOException while opening groovy source: " + name, ioe);
} finally {
if (cls == null) {
removeClassCacheEntry(name);
} else {
setClassCacheEntry(cls);
}
}
}
Source: src/main/groovy/lang/GroovyClassLoader.java#L733-L753
Here URL source = resourceLoader.loadGroovySource(name); it loads the full file URL to the source file and here cls = recompile(source, name, oldClass); it executes class compilation.
There are several phases involved in Groovy class compilation. One of them is Phase.SEMANTIC_ANALYSIS which analyses class fields and their types for instance. At this point ClassCodeVisitorSupport executes visitClass(ClassNode node) for MyClass class and following line
node.visitContents(this);
starts class contents processing. If we take a look at the source code of this method:
public void visitContents(GroovyClassVisitor visitor) {
// now let's visit the contents of the class
for (PropertyNode pn : getProperties()) {
visitor.visitProperty(pn);
}
for (FieldNode fn : getFields()) {
visitor.visitField(fn);
}
for (ConstructorNode cn : getDeclaredConstructors()) {
visitor.visitConstructor(cn);
}
for (MethodNode mn : getMethods()) {
visitor.visitMethod(mn);
}
}
Source: src/main/org/codehaus/groovy/ast/ClassNode.java#L1066-L108
we will see that it analyses and process class properties, fields, constructors and methods. At this phase it resolves all types defined for these elements. It sees that there are two properties m1 and m2 with types MyOuter and MyOuter.MyInner accordingly, and it executes visitor.visitProperty(pn); for them. This method executes the one we are looking for - resolve()
private boolean resolve(ClassNode type, boolean testModuleImports, boolean testDefaultImports, boolean testStaticInnerClasses) {
resolveGenericsTypes(type.getGenericsTypes());
if (type.isResolved() || type.isPrimaryClassNode()) return true;
if (type.isArray()) {
ClassNode element = type.getComponentType();
boolean resolved = resolve(element, testModuleImports, testDefaultImports, testStaticInnerClasses);
if (resolved) {
ClassNode cn = element.makeArray();
type.setRedirect(cn);
}
return resolved;
}
// test if vanilla name is current class name
if (currentClass == type) return true;
String typeName = type.getName();
if (genericParameterNames.get(typeName) != null) {
GenericsType gt = genericParameterNames.get(typeName);
type.setRedirect(gt.getType());
type.setGenericsTypes(new GenericsType[]{ gt });
type.setGenericsPlaceHolder(true);
return true;
}
if (currentClass.getNameWithoutPackage().equals(typeName)) {
type.setRedirect(currentClass);
return true;
}
return resolveNestedClass(type) ||
resolveFromModule(type, testModuleImports) ||
resolveFromCompileUnit(type) ||
resolveFromDefaultImports(type, testDefaultImports) ||
resolveFromStaticInnerClasses(type, testStaticInnerClasses) ||
resolveToOuter(type);
}
Source: src/main/org/codehaus/groovy/control/ResolveVisitor.java#L343-L378
This method gets executed for both MyOuter and MyOuter.MyInner classes. It is worth mentioning that class resolving mechanism only checks if given class is available in the classpath and it does not load or parse any classes. That is why MyOuter gets recognized when this method reaches resolveToOuter(type). If we take a quick look at its source code we will understand why it works for this class:
private boolean resolveToOuter(ClassNode type) {
String name = type.getName();
// We do not need to check instances of LowerCaseClass
// to be a Class, because unless there was an import for
// for this we do not lookup these cases. This was a decision
// made on the mailing list. To ensure we will not visit this
// method again we set a NO_CLASS for this name
if (type instanceof LowerCaseClass) {
classNodeResolver.cacheClass(name, ClassNodeResolver.NO_CLASS);
return false;
}
if (currentClass.getModule().hasPackageName() && name.indexOf('.') == -1) return false;
LookupResult lr = null;
lr = classNodeResolver.resolveName(name, compilationUnit);
if (lr!=null) {
if (lr.isSourceUnit()) {
SourceUnit su = lr.getSourceUnit();
currentClass.getCompileUnit().addClassNodeToCompile(type, su);
} else {
type.setRedirect(lr.getClassNode());
}
return true;
}
return false;
}
Source: src/main/org/codehaus/groovy/control/ResolveVisitor.java#L725-L751
When Groovy class loader tries to resolve MyOuter type name it reaches
lr = classNodeResolver.resolveName(name, compilationUnit);
which locates script with a name MyOuter.groovy and it creates a SourceUnit object associated with this script file name. It is simply something like saying "OK, this class is not in my classpath at the moment, but there is a source file I can see that once compiled it will provide a valid type of name MyOuter". This is why it finally reaches:
currentClass.getCompileUnit().addClassNodeToCompile(type, su);
where currentClass is an object associated with MyClass type - it adds this source unit to MyClass compilation unit, so it gets compiled with the MyClass class. And this is the point where resolving
MyOuter m1
class property ends.
In the next step it picks MyOuter.MyInner m2 property and it tries to resolve its type. Keep in mind - MyOuter got resolved correctly, but it didn't get loaded to the classpath, so it's static inner class does not exist in any scope, yet. It goes through the same resolving strategies as MyOuter, but any of them works for MyOuter.MyInner class. And this is why ResolveVisitor.resolveOrFail() eventually throws this compilation exception.
Workaround
OK, so we know what happens, but is there anything we can do about it? Luckily, there is a workaround for this problem. You can run your program and load MyClass successfully only if you load MyOuter class to Groovy script engine first:
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import groovy.util.GroovyScriptEngine;
public class TestGroovyScriptEngine {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException {
final File myGroovySourceDir = new File("C:/MyGroovySourceDir");
final URL[] urls = { myGroovySourceDir.toURL() };
GroovyScriptEngine groovyScriptEngine = new GroovyScriptEngine(urls,
Thread.currentThread().getContextClassLoader());
groovyScriptEngine.getGroovyClassLoader().loadClass("MyOuter");
Class<?> clazz = groovyScriptEngine.getGroovyClassLoader().loadClass("MyClass");
}
}
Why does it work? Well, semantic analysis of MyOuter class does not cause any problems, because all types are known at this stage. This is why loading MyOuter class succeeds and it results in Groovy script engine instance knows what MyOuter and MyOuter.MyInner types are. So when you next load MyClass from the same Groovy script engine it will apply different resolving strategy - it will find both classes available to the current compilation unit and it wont have to resolve MyOuter class based on its Groovy script file.
Debugging
If you want to examine this use case better it is worth to run a debugger and see analyze what happens at the runtime. You can create a breakpoint at line 357 of ResolveVisitor.java file for instance, to see described scenario in action. Keep in mind one thing though - resolveFromDefaultImports(type, testDefaultImports) will try to lookup MyClass and MyOuter classes by applying default packages like java.util, java.io, groovy.lang etc. This resolve strategy kicks in before resolveToOuter(type) so you have to patiently jump through them. But it is worth it to see and get a better understanding about how things work. Hope it helps!

Related

NoClassDefFoundError for inner class when renaming class with ASM

I'm trying to rename a class using ASM before writing it out to a JAR file which then gets loaded back in later. I've implemented my ASM remapper as follows:
private static class MyClassRemapper extends Remapper {
private final String className;
public MyClassRemapper(Class cls) {
className = cls.getCanonicalName().replace(".", "/");
}
#Override public String map(String internalName) {
if (internalName.startsWith(className))
return internalName.replace(className, "New" + className);
return super.map(internalName);
}
}
It all works fine if I feed it some OuterClass. However, if I then add an inner class InnerClass to OuterClass, then after I reload the JAR when I try to call a method (via reflection if that matters) on an instance of NewOuterClass I get the error:
java.lang.NoClassDefFoundError: com/.../NewOuterClass$InnerClass
at java.base/java.lang.Class.getDeclaredMethods0(Native Method)
at java.base/java.lang.Class.privateGetDeclaredMethods(Class.java:3139)
at java.base/java.lang.Class.getMethodsRecursive(Class.java:3280)
at java.base/java.lang.Class.getMethod0(Class.java:3266)
at java.base/java.lang.Class.getMethod(Class.java:2063)
at ...
From the error it's clear that ASM is succeeding in renaming some of the references to InnerClass but obviously not the class definition itself. I've looked at implementing mapInnerClassName but I'm pretty sure that I don't need to do that, as that's altering InnerClass itself.
Anyone have any ideas?
Edit: If I change the map function to;
#Override public String map(String internalName) {
if (internalName == className)
return internalName.replace(className, "New" + className);
return super.map(internalName);
}
so that only the top-level class is renamed, then I run into a different error when I attempt to run the inner class constructor:
NoSuchMethodError: com.(...).OuterClass$InnerClass.<init>(Lcom/.../NewOuterClass)V
which suggests that the methods of the inner class is failing to be renamed properly.
Holger in his comment provided the solution. The problem was that I was only remapping the class file of the outer class. However the inner class also has its own class file. The solution is to apply MyClassRemapper to that and write that out as well.

Does a GroovyMock of a non-static method have a specified behaviour?

I put this in my Spock test:
GroovyMock( File, global: true)
File.createNewFile() >> null
... which I realise is unorthodox/silly/curious: createNewFile is a non-static method.
The code involved is like this:
if( indexInfoFile.createNewFile() ) {
... it turns out from my experiements that mocking createNewFile like this always returns false, even if you try putting a block in the mock:
GroovyMock( File, global: true)
File.createNewFile() >> {
log.info( 'Hello mum!')
}
... the log message is not printed but createNewFile again returns false.
This is actually what I wanted (i.e. to mock a false return from createNewFile).
Is this intentional, documented behaviour?
PS Caveat: from my experience/experiments today, there is no doubt that this mock method does replace all occurrences of an invocation of this method, on any File instance. However, it appears also to have some alarming side-effects too: for example, a directory I created in my given block before the 2 GroovyMock lines is found NOT to exist afterwards, still in the given block, when I went
myDirPath.toFile().exists()
... I assume this is because toFile involves an invocation of createNewFile...
As documented, Groovy mocks only have additional "magic" when used with Groovy classes, but I assume that you are trying to mock java.io.File, which is a Java JRE class. Thus, the Groovy mock will behave like a normal Spock mock. So I don't know why you want to use the Groovy mock in the first place - maybe because you want to use the global: true feature in order to avoid refactoring for testability in your application class.
As you do not show us an MCVE, I have no way of knowing whether indexInfoFile can be injected into your class/method under test or if it is a dependency created inside the method. In the latter case you need to refactor, it is as simple as that. Dependencies should be injectable, period.
As for your code snippets, there are a few things wrong with them:
Method File.createNewFile() returns boolean, so it does not make any sense to stub it to return null.
When creating a mock, all methods will automatically return false, null or 0, depending on their return type. So there is no need to stub the result for createNewFile() in the first place if you want it to return false because it already does.
You cannot stub an instance method by trying to override it like it was a static method. It makes no sense. Please learn Spock syntax first.
Now, assuming your class under test looks like this (already prepared or refactored for dependency injection via method argument, constructor argument or setter)...
package de.scrum_master.stackoverflow.q59842227;
import java.io.File;
import java.io.IOException;
import java.util.Random;
public class FileCreator {
private static final Random RANDOM = new Random();
public boolean createIndexInfoFile(File indexInfoFile) throws IOException {
if (indexInfoFile.createNewFile()) {
System.out.println("File \"" + indexInfoFile + "\" created");
return true;
}
System.out.println("File \"" + indexInfoFile + "\" NOT created");
return false;
}
public static void main(String[] args) throws IOException {
new FileCreator().createIndexInfoFile(
new File("_abc_" + RANDOM.nextInt(10000) + ".txt")
);
}
}
... then you can test it like this:
package de.scrum_master.stackoverflow.q59842227
import spock.lang.Specification
class FileCreatorTest extends Specification {
def "index info file created"() {
given:
File file = Mock() {
createNewFile() >> true
}
expect:
new FileCreator().createIndexInfoFile(file)
}
def "no index info file created"() {
given:
File file = Mock()
expect:
!new FileCreator().createIndexInfoFile(file)
}
}
See? There is no need for global or Groovy mocks, normal mocks will do just fine. But you need to make your code testable instead of using fancy tricks.

How to provide java security for white listed packages using java security manager?

Problem:
I am trying to provide restriction (blacklisting ) all and allow only what I provided when we execute groovy using GroovyClassLoader
I am able to execute custom policy using with limited permission for GroovyClassLoader only.
Now I am trying to provide package restriction going to use as part of groovy execution. Let say If I allowed com.x.y this package if any package other then this used in groovy should throw SecurityException
I have tried to achieve the same with custom security manager and overriding the checkPackageAccess but didn't get success.
public TestSecurityManager extends SecurityManager{
List<String> whiteListedPkgList;
public void checkPackageAccess(String pkg){
if(!pkg.startWith(any of given white list pkg)){
throw new SecurityException("Access Denied");
}
//If package not belong to whilelisted package list throw security exception
}
}
When I tried using above approach we need to provide all packages for execution like com, java etc instead of java.nio.file. in whitelist list
UPDATE
If we try to allow package like com.x.y using white list comparison using start with check access pkg, it will allow that package but later on it will throw security exception for com package.
Could any one help for the same how we can achieve it ?
Thanks in advance
If you're able to, instead of using Java's SecurityManager, using the Groovy DSL features you can more easily achieve this.
See https://www.groovy-lang.org/dsls.html#_secure_ast_customizer
Example:
import groovy.transform.CompileStatic
import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.customizers.CompilationCustomizer
import org.codehaus.groovy.control.customizers.SecureASTCustomizer
#CompileStatic
class Main {
static final CompilationCustomizer scz = new SecureASTCustomizer().with {
closuresAllowed = false // user will not be able to write closures
methodDefinitionAllowed = false // user will not be able to define methods
importsWhitelist = [] // empty whitelist means imports are disallowed
staticImportsWhitelist = [] // same for static imports
staticStarImportsWhitelist = ['java.lang.Math'] // only java.lang.Math is allowed
constantTypesClassesWhiteList = [
Integer,
Float,
Long,
Double,
BigDecimal,
Integer.TYPE,
Long.TYPE,
Float.TYPE,
Double.TYPE,
Object,
String,
].asImmutable() as List<Class>
// method calls are only allowed if the receiver is of one of those types
// be careful, it's not a runtime type!
receiversClassesWhiteList = [
Math,
Integer,
Float,
Double,
Long,
BigDecimal,
PrintStream,
Object,
].asImmutable() as List<Class>
it
}
static void main(args) {
def configuration = new CompilerConfiguration()
configuration.addCompilationCustomizers(scz)
// evaluate sandboxed code
new GroovyShell(configuration).evaluate(
""" println 'hello world' """)
}
}
If all you need is to whitelist certain classes, you can also try writing your own class loader and using that to evalute the sandboxed script:
class MyClassLoader extends ClassLoader {
Set<String> whiteListPackages = [
'java.lang.', 'java.util.', 'groovy.', 'org.codehaus.groovy.', 'Script'
]
MyClassLoader(ClassLoader parent) {
super(parent)
}
#Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (!whiteListPackages.any { okPkg -> name.startsWith(okPkg) }) {
throw new ClassNotFoundException('Access is forbidden')
}
return super.loadClass(name, resolve)
}
}
def shell = new GroovyShell(new MyClassLoader(GroovySystem.classLoader))
// evaluate the script with our own classloader
shell.evaluate('''
println 'hello'
println([1,2,3])
// This line throws an error because the `java.net` package is not whitelisted
println(new URL('https://groovy-lang.org'))
''')

How to reference subclass from a static superclass method in Groovy

A simplified version of what I'm trying to do in Groovy:
class Animal {
static def echo() {
println this.name // ie "class.name"
}
}
class Dog extends Animal {
}
class Cat extends Animal {
}
Dog.echo()
Cat.echo()
// Output:
// => Animal
// => Animal
//
// What I want:
// => Dog
// => Cat
I think what I'm asking here is: when I call a static method on an object, and
the static method is defined in the object's superclass, is there a way to obtain
the actual type of the object?
A static method is not defined in the object context, but in the class context. You might get confused by the presence of this in the Groovy static method. However, it's only a syntactic sugar that eventually replaces this.name with Animal.class.name.
If you compile the Animal class from your example with a static compilation enabled, you will see that it compiles to the following Java equivalent (result after decompiling the .class file):
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import groovy.lang.GroovyObject;
import groovy.lang.MetaClass;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
public class Animal implements GroovyObject {
public Animal() {
MetaClass var1 = this.$getStaticMetaClass();
this.metaClass = var1;
}
public static Object echo() {
DefaultGroovyMethods.println(Animal.class, Animal.class.getName());
return null;
}
}
You can see that the following line in the echo method:
DefaultGroovyMethods.println(Animal.class, Animal.class.getName());
operates directly on the Animal class name. So from the echo method perspective, it doesn't matter how many classes extend it. As long as those classes invoke echo method defined in the Animal class, you will always see Animal printed as a result.
And there is even more than that. If you use the following compiler configuration script:
config.groovy
withConfig(configuration) {
ast(groovy.transform.CompileStatic)
ast(groovy.transform.TypeChecked)
}
and then compile the script (let's call it script.groovy) using this configuration option with the following command:
groovyc --configscript=config.groovy script.groovy
then you will see something like this after decompiling the .class file:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import groovy.lang.Binding;
import org.codehaus.groovy.runtime.InvokerHelper;
public class script extends groovy.lang.Script {
public script() {
}
public script(Binding context) {
super(context);
}
public static void main(String... args) {
InvokerHelper.runScript(script.class, args);
}
public Object run() {
Animal.echo();
return Animal.echo();
}
}
You can see that even though you have invoked Dog.echo() and Cat.echo() in your Groovy script, the compiler replaced these calls with the double Animal.echo() invocation. It happened because calling this static method on any other subclass does not make any difference.
Possible solution: applying double dispatch
There is one way to get the expected output - override echo static method in Dog and Cat class. I can assume that your real method may do something more than the exemplary echo method you have shown above, so you might need to call the super echo method from a parent class. But... there are two problems: (1) you can't use super.echo() in the static context, and (2) it doesn't solve the problem, because parent method still operates in the Animal class context.'
To solve this kind of issue you might want to mimic a technique called double dispatch. In short - when we don't have information about the caller in the method that was called, let's allow the caller to pass this information with the method call. Consider the following example:
import groovy.transform.CompileStatic
#CompileStatic
class Animal {
// This is a replacement for the previous echo() method - this one knows the animal type from a parameter
protected static void echo(Class<? extends Animal> clazz) {
println clazz.name
}
static void echo() {
echo(Animal)
}
}
#CompileStatic
class Dog extends Animal {
static void echo() {
echo(Dog)
}
}
#CompileStatic
class Cat extends Animal {
static void echo() {
echo(Cat)
}
}
Animal.echo()
Dog.echo()
Cat.echo()
This may sound like a boilerplate solution - it requires implementing echo method in each subclass. However, it encapsulates the echo logic in the method that requires Class<? extends Animal> parameter, so we can let every subclass to introduce their concrete subtype. Of course, this is not a perfect solution. It requires implementing echo method in each subclass, but there is no other alternative way. Another problem is that it doesn't stop you from calling Dog.echo(Animal) which will cause the same effect as calling Animal.echo(). This double dispatch like approach is more like introducing a shorthand version of echo method which uses the common static echo method implementation for simplicity.
I don't know if this kind of approach solves your problem, but maybe it will help you find a final solution.

importing groovy scripts from other scripts dynamically

I am trying to load 2 groovy scripts from a remote location (say S3). The first script is straight forward. It names a package a.b and the file itself is called c.dsl.
package a.b;
int i = 10;
public void anotherfunc() {};
private void anotheractivityFunc() { int k = 9;};
The second groovy script has the following code in it (it is named s.dsl)
package c.d;
// Notice the import of the other script file here
import a.b.c;
int i = 10;
c myself;
public void func() {};
private void activityFunc()
{
int k = 9;
c nbn;
};
This script is trying to create an object of the class defined in the first script. I run this through with this code
CustomClassLoader loader = new CustomClassLoader(GroovyDSLTest.class.getClassLoader(), configuration);
GroovyShell shell = new GroovyShell(loader, configuration);
Script script = shell.parse(scriptText, "s.dsl");
CustomClassLoader is defined with this function
public Class loadClass(name, ...)
{
try
{
return super.loadClass(name, lookupScriptFiles, preferClassOverScript, resolve);
}
catch (ClassNotFoundException e)
{
if (name.equals("a.b.c"))
// Notice how I handle the import a.b.c from the second groovy file
return parseClass(<text from c.dsl>, "c.dsl");
throw e;
}
}
I get a compile error when I do this. It loads the a.b.c file cleanly. But when I try to make a member variable "c" it fails.
s.dsl: 1: [Static type checking] - The variable [myself] is undeclared.
# line 8, column 3.
c myself;
^
s.dsl: 1: [Static type checking] - Cannot find matching method c.d.s#c(java.lang.Object). Please check if the declared type is right and if the method exists.
# line 8, column 1.
c myself;
^
Why is it not able to find c? I was able to print the classloader as it loads classes and it did load a.b.c. Changing the above to use a.b.c instead of just "c" does not work as well. What is the name of the class that groovy generates from the script file. I want to be able to access this class and create objects of it.
How about using #BaseScript?
You can create a class that you want to import:
abstract class Something extends Script {
...
}
And #BaseScript can help you to import it: #groovy.transform.BaseScript Something something
See: http://docs.groovy-lang.org/2.5.2/html/gapi/groovy/transform/BaseScript.html
Grrrr... I figured this out.
a) one needs to call in loadClass setClassCacheEntry();
b) Also naming a class "c" does not seem to work. I renamed it to MyClass and it seems to work

Resources