Groovy class can't locate superclass in the same package - java

I have a few groovy classes in the same package ( com.company.config ) in a multi-modular java-8 project.
All used to inherit a java interface ( MyInterface ), but some refactoring was needed so I created a groovy abstract class which resides in the same package as the other scripts; it implements MyInterface and is inherited by the other scripts.
Ever since this change, I can't seem to execute the scripts from java code anymore.
In particular, GroovyClassLoader::parseClass(File) throws:
org.codehaus.groovy.control.MultipleCompilationErrorsException:
startup failed:<|C:\my_workspace\a_project_name\modules\module-setup\src\com\company\config\MyScript1Impl.groovy:
7: unable to resolve class com.company.config.AbstractGroovyClass
# line 7, column 1.
import com.company.config.AbstractGroovyClass
^
At line 7, you can indeed find the import declaration
import com.company.config.AbstractGroovyClass
which I added (despite the class being in the same package) after the first time the same error was thrown and after I read this.
The Exception is triggered in the following line in the java code:
public Object getInstance(File sourceFile) {
try {
GroovyClassLoader gcl = new GroovyClassLoader();
Class clazz = gcl.parseClass(sourceFile); // << Here
Object inst = clazz.newInstance();
// ....
}
// ...
}
whereas I call this function with the following parameters
getInstance(
new File("./modules/module-setup/src/com/company/config/"
className + ".groovy" // className = "MyScript1Impl" in this case
)
);
As already stated, before the introduction of the abstract class, everything was working fine.
Why can't the groovy class find its superclass in the same package, even with the import declaration?
This is the stack-trace of the internal calls:
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed ...
at org.codehaus.groovy.control.ErrorCollector.failIfErrors(ErrorCollector.java:310)
at org.codehaus.groovy.control.CompilationUnit.applyToSourceUnits(CompilationUnit.java:946)
at org.codehaus.groovy.control.CompilationUnit.doPhaseOperation(CompilationUnit.java:593)
at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:542)
at groovy.lang.GroovyClassLoader.doParseClass(GroovyClassLoader.java:298
at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:268)
at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:254)
at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:195)
The rest stack trace is relative to the application calls so it's unhelpful.
Edit I
I noticed indeed that the GroovyClassLoader does not know anything about other groovy classes and where they are located so I just added "./modules/module-setup/src/com/company/config/" in
GroovyClassLoader::addClassPath(String)
but I'm getting the same result as before.
The path is surely correct as the File instance is created with it and can be opened by the class loader.

I resolved it temporarily by loading the superclass via GroovyClassLoader::parseClass right before loading the actual inherited class.
final GroovyClassLoader gcl = new GroovyClassLoader();
// ...
// load superclass first
Class<?> abstractClass = gcl.parseClass(new File(classPath, "AbstractGroovyClass.groovy"));
// load the actual script
Class<?> clazz = gcl.parseClass(sourceFile);
It's definitely a bad answer as, if I had more groovy classes on which I depend on, I would have to manually parse them one by one. But it works...
Hoping someone can give a better answer.

Related

getSuperClass() throws Exception but getSuperclassName() returns the name of the super class

System.out.println(javaClass.getSuperclassName());
JavaClass javaClass1 = javaClass.getSuperClass();
the first line output the name of the class: RestController
The second line throws Exception:
java.lang.ClassNotFoundException: Exception while looking for class example.RestController: java.io.IOException: Couldn't find: example/RestController.class
So, you're using The Byte Code Engineering Library (Apache Commons BCEL™) to load classes from a Jar file (by filename) and want to print the entire call graph with the following github project.
This works just fine, until you want to ask for the superclass of a class that was found in your jar file.
So bcel will load a .class file, and store all it can read from the class file in a JavaClass model. This is for example the name, some flags, the super class name, the declared methods etc.
For example inspect the following java code;
JavaClass stringClass = Repository.lookupClass("java.lang.String");
System.out.println(stringClass);
with output:
public final class java.lang.String extends java.lang.Object
implements java.io.Serializable, java.lang.Comparable, java.lang.CharSequence
file name java.lang.String
compiled from String.java
compiler version 52.0
access flags 49
constant pool 540 entries
ACC_SUPER flag true
Attribute(s):
SourceFile: String.java
etc...
So bcel knows that the superclass is java.lang.Object, but it has not loaded any of the classes at this point! For JRE classes this is of course moot, but for the classes from your Jar file this is a problem.
Because org.apache.bcel.classfile.JavaClass#getSuperclassName will just return the String value that it found as the super class in the .class file. Again this class was not loaded, so the Repository doesn't know about it.
When you then ask for the org.apache.bcel.classfile.JavaClass#getSuperClass, it will try to find it like so:
public JavaClass getSuperClass() throws ClassNotFoundException {
return "java.lang.Object".equals(this.getClassName()) ? null : this.repository.loadClass(this.getSuperclassName());
}
Bcel will try to load it from its Respository, and if the class is unknown, it will delegate the loading to the current ClassPath. Since you're just inputting a File pointing to a Jar, this will fail with the ClassNotFoundException.
There are two ways to you can solve this:
Put the jar file(s) on your classpath; for example via Including all the jars in a directory within the Java classpath
First load all the jar files into the Repository of bcel, so that it knows these classes exist. If we stay in the JCallGraph example from github, that would look something like this:
// ... JCallGraph code from Github above this point
try (JarFile jar = new JarFile(f)) {
// extra stream over jar entries
Stream<JarEntry> jarEntryStream = enumerationAsStream(jar.entries());
jarEntryStream.filter(jarEntry -> jarEntry.getName().endsWith(".class"))
.forEach(jarEntry -> {
ClassParser cp = new ClassParser(jarFileName, jarEntry.getName());
try {
// here we tell BCEL this class exists
Repository.addClass(cp.parse());
} catch (IOException ex) {
throw new RuntimeException(ex);
}
});
// ... back to the JCallGraph code from Github
Stream<JarEntry> entries = enumerationAsStream(jar.entries());
Note that if you have multiple jar files, or super classes coming from external dependencies, these all need to be on the classpath (1) or loaded in bcel first (2) before you can load the superclass.

Why Class.forName thowing a ClassNotFoundException even though I'm specifically importing that exact class?

I have the following code:
Class<?> classType = Class.forName(typeClassName);
It keeps throwing an error everytime I try to run the code:
java.lang.ClassNotFoundException: EmailAddress
But I'm speciaifially importing the EmailAddress class into the class that I'm running the first code:
import ie.folder.EmailAddress;
How can this be?
If you are already importing the class there is no need to use reflection, you can just do
Class<EmailAddress> clazz = EmailAddress.class;
You would only really need to use Class.forName if you do not know the class name at the time you compile your program. If you still want to do that, you need to use the fully qualified class name (the imports do not matter, they are not considered at runtime, only during compilation).
Class<?> clazz = Class.forName("ie.folder.EmailAddress");
And you have to deal with the exception if that class could not be found or loaded.

How can I change classloader of getSystemJavaCompiler

I am dynamically compiling Java sources using the Java compiler API. My generated source files inherit from com.example.BaseClass, which is just a normal class, not dynamically generated. The generated Java sources look like this:
public class Foo implements com.example.BaseClass
{
#Override
public Integer getAnswer(com.example.Context context) throws Exception
{
return ...;
}
}
All works fine when running in IDE, but after packaging into a Springboot jar, my com.example.BaseClass is moved to BOOT-INF/classes/com.example.BaseClass. When dynamically compiling I now get:
/Foo.java:1: error: package com.example does not exist
public class Foo implements com.example.BaseClass
^
I try to change the classloader of the compiler so that the compiler will search in BOOT-INF/classes.
ClassLoader before = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(new CustomClassloader(before));
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
Thread.currentThread().setContextClassLoader(before);
However, debugging shows that my CustomClassloader.loadClass(String name) method is never called. More debugging showed that compiler.getClass().getClassloader() returns
java.net.FactoryURLClassLoader#39a5ae48
So, the CustomClassloader is not used by the Compiler instance. How can I get the Compiler to use my CustomClassloader? Better solutions for solving the compiling issue are also welcome ofcourse :-).
There are some oddities about how the java standard compiler does lookups and it doesn't always resolve out of the running class path correctly. Anyway, it does that resolution using the JavaFileManager.list call.
It will call it at least 4 times in the process of trying to look up your base class. Override a ForwardingJavaFileManager and pass that into getTask and have it lookup the resource and return it.
Alternately, you could use the Janino in-momeory compiler library which sets up a fake in memory file system ( no compiling to disk ) and still uses the plaform compiler and sorts out all this classpath nonsense for you.

NoClassDefFoundError on Non-static class with private constructor

I have a Java class that has a private constructor:
public class MyClass {
private static final MyClass myClass = new MyClass();
private MyClass() {}
public static MyClass getInstance() {
return myClass;
}
}
This class is being used in the application like this:
MyClass myClass = MyClass.getInstance();
The whole application is also exported as a JAR and used in another application.
When I try to do the same in another application (Where its being invoked from a JAR) I get the following error:
java.lang.NoClassDefFoundError: Could not initialize class com.example.MyClass
I am not sure if this is the required behavior for a class with a private constructor, or is there something else wrong with it?
Thanks!
Your error has nothing to do with static-ness or your constructor. From the javadocs:
Thrown if the Java Virtual Machine or a ClassLoader instance tries to
load in the definition of a class (as part of a normal method call or
as part of creating a new instance using the new expression) and no
definition of the class could be found. The searched-for class
definition existed when the currently executing class was compiled,
but the definition can no longer be found.
NoClassDefFoundError means the class definition is unavailable when you're trying to run your program. This is some kind of path error - either this class is not in the exported jar or it's not being included on the classpath when it's being run.
Are you sure you have correct included the JAR in the classpath of the second application?
NoClassDefFoundError Occurs when JVM tries to load a particular class that is the part of your code execution (as part of a normal method call or as part of creating an instance using the new keyword) and that class is not present in your classpath but was present at compile time because in order to execute your program you need to compile it and if you are trying use a class which is not present compiler will raise compilation error.

How to provide an interface to JavaCompiler when compiling a source file dynamically?

I'm trying to compile and load a class at runtime, without knowing the package of the class. I do know that the class should comply with an interface, and the location of the source, (and hence the class name). I'm trying the following:
/* Compiling source */
File root = new File("scripts");
File sourceFile = new File(root, "Test.java");
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
compiler.run(null, null, null, sourceFile.getPath());
where the Test.java file looks something like
import foo.Itest;
public class Test implements Itest{
...
}
And I get a cannot find symbol symbol : class Itest error from the compiler. How do I provide the compiler with the interface (which has already been loaded) to avoid this error?
[EDIT - RESOLVED]: The error came from the fact the the interface was ITest and the source referred to an Itest interface.
It seems likely that the compiler.run() is running externally and needs the class path to be set. Have you tried to pass it a suitable class path setting using the last parameter args to the run() call? Perhaps that's why ToolProvider.getSystemToolClassLoader().
This stackoverflow post might also help you.
Not sure if this is what you're looking for but, as mentioned by #Phil here, you could try to pass a classpath argument in your compiler.run method.
Have you considered generating your class with javassist or something like that?

Categories

Resources