Compile Groovy class at runtime in Java - java

I am successfully able to compile Groovy in Java at runtime and store it in a database and pull it out. I can't compile a Groovy class if it has inner classes or an inner enum. Has anyone successfully compiled Groovy code like this and included inner classes/enums and able to pull the script out by classname?
For example, I want to load the "Test" script shown below that contains inner classes and run the script at run time.
Compiler code:
public byte[] compileGroovyScript(final String className, final String script) {
byte[] compiledScriptBytes = null;
CompilationUnit compileUnit = new CompilationUnit();
compileUnit.addSource(className, script);
compileUnit.compile(Phases.CLASS_GENERATION);
for (Object compileClass : compileUnit.getClasses()) {
GroovyClass groovyClass = (GroovyClass) compileClass;
compiledScriptBytes = groovyClass.getBytes();
}
return compiledScriptBytes;
}
Code to pull script out:
public Class getGroovyScript(final String className, final byte[] script) {
Class clazz = null;
try (GroovyClassLoader classLoader = new GroovyClassLoader(this.getClass().getClassLoader())) {
clazz = classLoader.defineClass(className, script);
} catch (IOException e) {
} catch (Exception e) {
}
return clazz;
}
Code to run the script:
Class groovyClass = app.getGroovyScript(className, compiledScript);
TestScript script = (TestScript) groovyClass.newInstance();
System.out.println(script.getMessage());
Groovy script:
import com.groovy.groovy.TestScript
class Test implements TestScript {
String getMessage() {
[1..10].each(){
println it
}
return "Jello"
}
}

It isn't clear from the description why you are doing the compiling yourself. If you can just let Groovy do it for you then the whole thing can just be simplified to something like this:
String script = // string containing the script you want to parse
GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
Class theParsedClass = groovyClassLoader.parseClass(script);

Ok this may be a little late but hopefully it helps the next person. I think you need to save a List for each groovy class and then cl.defineClass and finally cl.loadClass. I think groovy sometimes compile to a list of classes basically as in below when I addSource(), I add one class and then loop over all the generated classes from that one file.
This is the code I am currently running(though I have not tried saving and reloading at a later time)
GroovyClassLoader cl = new GroovyClassLoader();
CompilationUnit compileUnit = new CompilationUnit();
compileUnit.addSource(scriptCode.getClassName(), scriptCode.getScriptSourceCode());
compileUnit.compile(Phases.CLASS_GENERATION);
compileUnit.setClassLoader(cl);
GroovyClass target = null;
for (Object compileClass : compileUnit.getClasses()) {
GroovyClass groovyClass = (GroovyClass) compileClass;
cl.defineClass(groovyClass.getName(), groovyClass.getBytes());
if(groovyClass.getName().equals(scriptCode.getClassName())) {
target = groovyClass;
}
}
if(target == null)
throw new IllegalStateException("Could not find proper class");
return cl.loadClass(target.getName());
take note of the cl.defineClass call which puts the class in the classloader so when it is looked up(the enum or innerclass), it will be there.
and so now I think you do not need to create your own class loader(though you avoid useless defineClass until it is needed with your own classloader which can be useful and more performant).

This forgoes any error handling for the sake of simplicity here, but this is probably what you want:
public byte[] compileGroovyScript(final String className, final String script) {
byte[] compiledScriptBytes = null;
CompilationUnit compileUnit = new CompilationUnit();
compileUnit.addSource(className, script);
compileUnit.compile(Phases.CLASS_GENERATION);
List classes = compileUnit.getClasses();
GroovyClass firstClass = (GroovyClass)classes.get(0);
compiledScriptBytes = firstClass.getBytes();
return compiledScriptBytes;
}

Depending on your requirements, you might want to provide access to the inner classes and you could do that with something like this which finds the class with the matching name instead of assuming the first class:
public byte[] compileGroovyScript(final String className, final String script) {
byte[] compiledScriptBytes = null;
CompilationUnit compileUnit = new CompilationUnit();
compileUnit.addSource(className, script);
compileUnit.compile(Phases.CLASS_GENERATION);
for (Object compileClass : compileUnit.getClasses()) {
GroovyClass groovyClass = (GroovyClass) compileClass;
if(className.equals(groovyClass.getName())) {
compiledScriptBytes = groovyClass.getBytes();
break;
}
}
return compiledScriptBytes;
}

I am running into this myself but having just done an on-demand java compiler at runtime, I believe you are running into the same issue I solved in this code
https://github.com/deanhiller/webpieces/tree/master/runtimecompile/src/main/java/org/webpieces/compiler/api
webpieces/runtimecompile is a re-usable on-demand java compiler using the eclipse compiler.
Now, for groovy, I think you are running into this case
1. you compile ONE script
2. this results in 'multiple' class file objects (I think) just like mine did
3. This is where you need to store EACH in the database SEPARATELY
4. Then you need a classloader that tries to lookup the 'inner classes' when jvm asks for it
5. finally you do a yourclassLoader.loadApplicationClass (much like the one in CompileOnDemandImpl.java in the project above
6. To be clear, step 5 causes step 4 to happen behind the scenes (and that is what is confusing).
If you step through the test case AnonymousByteCacheTest, it pretty much is doing something like that.
you don't need to install ANYTHING to run the build on that project, just clone it and "./gradlew test" and will pass and "./gradlew eclipse" or "./gradlew idea" and it generates IDE files so you can step through it.
It is very very similar. I am trying to get the groovy version working next myself.

Related

Java add Classpaths at runtime

There are many answers to this question in the stackoverflow?
But the most cast the ClassLoader.getSystemClassLoader() to URLClassLoader and this works anymore.
The classes must be found by the systemclassloader.
Is there an another solution?
- without restarting the jar
- without creating a own classloader (In this case I must replace the systemclassloader with my own)
The missing classes/jars must be added at the moment only on startup and I didn't want to add these in the manifest with "Classpath".
I found the Java Agent with the premain-Method. This can also work great, but in this case I want to start the premain method without calling "java -javaagent:... -jar ..."
Currently I restart my programm at the beginning with the missing classpaths:
public class LibLoader {
protected static List<File> files = new LinkedList<>();
public static void add(File file) {
files.add(file);
}
public static boolean containsLibraries() {
RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
String[] classpaths = runtimeMxBean.getClassPath().split(System.getProperty("path.separator"));
List<File> classpathfiles = new LinkedList<>();
for(String string : classpaths) classpathfiles.add(new File(string));
for(File file : files) {
if(!classpathfiles.contains(file)) return false;
}
return true;
}
public static String getNewClassPaths() {
StringBuilder builder = new StringBuilder();
RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
builder.append(runtimeMxBean.getClassPath());
for(File file : files) {
if(builder.length() > 0) builder.append(System.getProperty("path.separator"));
builder.append(file.getAbsolutePath());
}
return builder.toString();
}
public static boolean restartWithLibrary(Class<?> main, String[] args) throws IOException {
if(containsLibraries()) return false;
List<String> runc = new LinkedList<>();
runc.add(System.getProperty("java.home") + "\\bin\\javaw.exe");
RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
List<String> arguments = runtimeMxBean.getInputArguments();
runc.addAll(arguments);
File me = new File(LibLoader.class.getProtectionDomain().getCodeSource().getLocation().getPath());
String classpaths = getNewClassPaths();
if(!classpaths.isEmpty()) {
runc.add("-cp");
runc.add(classpaths);
}
if(me.isFile()) {
runc.add("-jar");
runc.add(me.getAbsolutePath().replace("%20", " "));
} else {
runc.add(main.getName());
}
for(String arg : args) runc.add(arg);
ProcessBuilder processBuilder = new ProcessBuilder(runc);
processBuilder.directory(new File("."));
processBuilder.redirectOutput(Redirect.INHERIT);
processBuilder.redirectError(Redirect.INHERIT);
processBuilder.redirectInput(Redirect.INHERIT);
Process process = processBuilder.start();
try {
process.waitFor();
} catch (InterruptedException e) {
e.printStackTrace();
}
return true;
}
}
Hope someone has a better solution.
Problem is, the classes must be found my the system ClassLoader not by a new ClassLoader.
It sound like your current solution of relaunching the JVM is the only clean way to do it.
The system ClassLoader cannot be changed, and you cannot add extra JARs to it at runtime.
(If you tried to use reflection to mess with the system classloader's data structures, at best it will be non-portable and version dependent. At worst it will be either error prone ... or blocked by the JVM's runtime security mechanisms.)
The solution suggested by Johannes Kuhn in a comment won't work. The java.system.class.loader property is consulted during JVM bootstrap. By the time your application is running, making changes to it will have no effect. I am not convinced that the approach in his Answer would work either.
Here is one possible alternative way to handle this ... if you can work out what the missing JARs are early enough.
Write yourself a Launcher class that does the following:
Save the command line arguments
Find the application JAR file
Extract the Main-Class and Class-Path attributes from the MANIFEST.MF.
Work out what the real classpath should be based on the above ... and other application specific logic.
Create a new URLClassLoader with the correct classpath, and the system classloader as its parent.
Use it to load the main class.
Use reflection to find the main classes main method.
Call it passing the save command line arguments.
This is essentially the approach that Spring Bootstrap and OneJar (and other things) take to handle the "jars in a jar" problem and so on. It avoids launching 2 VMs.

Integrating Groovy RootLoader with Java 8 SystemClassLoader

The following is deep inside a library I use. In 2015 this worked with Groovy 2.3 and early versions of 2.4, probably with Java 6 or 7! I wanted to update to Java 8 before trying to modify for Java9+.
final class DynamicClassLoader extends ClassLoader {
final NodeID originatingNode;
NetChannelOutput requestClassData;
NetChannelInput classDataResponse = NetChannel.net2one();
final Hashtable classes = new Hashtable();
DynamicClassLoader(NodeID originator, NetChannelLocation requestLocation) {
super(ClassLoader.getSystemClassLoader());
this.originatingNode = originator;
this.requestClassData = NetChannel.one2net(requestLocation);
}
...
}
When I try to invoke the code from Groovy I get the following error:
org.codehaus.groovy.tools.RootLoader cannot be cast to jcsp.net2.mobile.DynamicClassLoader
The Point where this is called from is given in the following code at the line indicated by **
public byte[] filterTX(Object obj)
throws IOException
{
ClassLoader loader = obj.getClass().getClassLoader();
byte[] bytes = this.internalFilter.filterTX(obj);
if (loader == ClassLoader.getSystemClassLoader() || loader == null)
{
DynamicClassLoaderMessage message = new DynamicClassLoaderMessage(Node.getInstance().getNodeID(),
(NetChannelLocation) ClassManager.in.getLocation(), bytes);
byte[] wrappedData = this.internalFilter.filterTX(message);
return wrappedData;
}
**DynamicClassLoader dcl = (DynamicClassLoader)loader;**
DynamicClassLoaderMessage message = new DynamicClassLoaderMessage(dcl.originatingNode,
(NetChannelLocation) ClassManager.in.getLocation(), bytes);
byte[] wrappedData = this.internalFilter.filterTX(message);
return wrappedData;
}
After discussion with the Groovy community I discoverd that the problem lay in the way the Intellij invokes Groovy scripts. The code works in Eclipse without any problem. In Intellij it was necessary to create a jar artifact for each of the scripts I wanted to run in parallel, which I could then run from a command line interface. I recoded the application in Java 8 and it worked with no problem. Hope that helps.

Correct way to instantiate a groovy class

I want to instatiate a groovy class and i have some concerns
My first choice is to use GroovyShell :
groovy-script:
class Foo {
public String doStuff(String stuff) {
return stuff + "_utils";
}
}
new Foo(); // ??
main-script :
GroovyShell shell = new GroovyShell();
Script script = shell.parse(new File(path));
def clazz = script.run();
String result = clazz.doStuff("test");
print(result); // test_utils;
The second option is to use GroovyClassLoader :
groovy-script
class Foo {
public String doStuff(String stuff) {
return stuff + "_utils";
}
}
main-script
GroovyClassLoader loader = new GroovyClassLoader();
Script script = loader.parseClass(new File(path))
Object clazz = script.newInstance();
Object[] args = new Object[1];
args[0] = "test";
String result = clazz.invokeMethod("doStuff", args);
print(result) // test_utils
Both will run fine, i would prefer to use GroovyShell because i use it everywhere in my current code, but i don't know if new Foo() inside my scripts can cause any memory leaks. Is it possible?
GroovyShell uses the default GroovyClassLoader. So if you don't need any extra specific features that are provided by GroovyClassLoader, you should stick to GroovyShell to keep it simple. GroovyShell and GroovyClassLoader are instances of garbage collected and I don't believe there are memory leaks for neither of them.
After #daggett's help i managed to find the solution that fits my needs.
I will not use groovy classes at all.
A simple example
utility groovy script :
String doStuff() {
return "doStuff";
}
String doStuff2(){
return "doStuff2";
}
calling the utility methods from the main groovy script
GroovyShell shell = new GroovyShell();
Script utilsScript = shell.parse(new File(PATH_TO_UTIL_SCRIPT));
String result = utilsScript.doStuff();
println(result); // doStuff;
String result2 = utilsScript.doStuff2();
println(result2); // doStuff2;
This is not the answer to the original question but since it fits my need i am fine.

How to run groovy in Java

Hi everyone tried different ways to run a groovy in java with no luck, had read some documentation but things aren't that clear at the moment.
Anyone may know how to run this groovy?
package com.test.dev.search;
public class SearchQueryBase implements SearchQuery {
public QueryString getMatterQuery( SearchFilter filter ) {
String[] terms = filter.getSearchTerm().toLowerCase().split( " " );
...
...
...
}
}
This is a .groovy file (the one from above), I've tried the follow to run it without luck.
Down here is the Java class in which I want to run the above Groovy and execute getMatterQuery() to see the output from java main.
public static void main(String args[]) throws CGException {
String TEMPLATE_PACKAGE_PREFIX = "<path_to_groovy_file.";
String templateFileName = TEMPLATE_PACKAGE_PREFIX + "SearchQueryBase";
SearchFilter test = null;
Binding binding = new Binding();
binding.setVariable("filter", test);
GroovyShell shell = new GroovyShell(binding);
shell.evaluate(templateFileName);
System.out.println("Finish");
}
EDIT #1
This is the error I'm getting when I run it;
Exception in thread "main" groovy.lang.MissingPropertyException: No such property: Common for class: Script1
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:50)
at org.codehaus.groovy.runtime.callsite.PogoGetPropertySite.getProperty(PogoGetPropertySite.java:49)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callGroovyObjectGetProperty(AbstractCallSite.java:231)
at Script1.run(Script1.groovy:1)
at groovy.lang.GroovyShell.evaluate(GroovyShell.java:580)
at groovy.lang.GroovyShell.evaluate(GroovyShell.java:618)
at groovy.lang.GroovyShell.evaluate(GroovyShell.java:589)
1.
the GroovyShell.evaluate(java.lang.String scriptText) accepts string as a groovy text (content), and you try to call it with filename instead. use shell.evaluate( new File(templateFileName) )
2.
you can continue using shell.evaluate( new File(...) ) but keep in your groovy file only content of the method getMatterQuery():
String[] terms = filter.getSearchTerm().toLowerCase().split( " " );
...
...
...
so you'll have groovy script, and your code should work
3.
if you want to keep groovy as a class and call the method getMatterQuery() from this class with parameter, then your java code should be like this:
import groovy.lang.*;
...
public static void main(String[]s)throws Exception{
GroovyClassLoader cl=new GroovyClassLoader();
//path to base folder where groovy classes located
cl.addClasspath(path_to_groovy_root);
//the groovy file with SearchQueryBase.groovy
//must be located in "com/test/dev/search" subfolder under path_to_groovy_root
Class c = cl.loadClass("com.test.dev.search.SearchQueryBase");
SearchQuery o = (SearchQuery) c.newInstance();
System.out.println( o.getMatterQuery(test) );
}

Elegant way to compare the content of JARs (to find new classes and methods)

Yes, the Internet says - "unzip them all, decompile and compare the code with some tool (total comander, WinCompare, meld(linux), whatever...) The reason why I need a tool to generate difference report automatically from Fodler1 and Folder2 is simple - there are too much JARs in these Folders and I need to compare these folders (with next version of Jars) say 1 time in a month. So, I really do not want to do it manually at all!
Let's see what I've got so far:
1) I can find all JARs in each Folder:)
2) I can get the list of classes from each JAR:
private static void AddAllClassesFromJAR(JarInputStream jarFile,
ArrayList<String> classes) throws IOException {
JarEntry jarEntry = null;
while (true) {
jarEntry = jarFile.getNextJarEntry();
if (jarEntry == null) {
break;
}
if (jarEntry.getName().endsWith(".class")) {
classes.add(jarEntry.getName());
}
}
}
public static List<String> getClasseNamesInPackage(String jarName) {
ArrayList<String> classes = new ArrayList<String>();
try {
JarInputStream jarFile = new JarInputStream(new FileInputStream(jarName));
AddAllClassesFromJAR(jarFile, classes);
} catch (Exception e) {
e.printStackTrace();
}
return classes;
}
3) There is Reflection in Java (Core Java 2 Volume I - Fundamentals, Example 5-5), so I can get the list of methods from one class once I know its name.
In order to do that I need to make an instance of each class, the problem is how can I make the instance of each Class which I got from each JAR file?
Now I'm loading each JAR:
loader_left = new JarClassLoader("left/1.jar");
public class JarClassLoader extends URLClassLoader {
public JarClassLoader( URL url ) {
super( new URL[]{url} );
}
public JarClassLoader( String urlString ) throws MalformedURLException {
this( new URL( "jar:file://" + urlString + "!/" ) );
}
No exceptions, but I can not find any resource in it, trying to load the class like:
class_left = loader_left.loadClass("level1.level2.class1");
And getting "java.lang.ClassNotFoundException".
Any glue where is the problem? (class name is verified. it is hardcoded just for testing, ideally it should get it from the list of the classes)
Second question: since most of the classes in Folder1 and Folder2 will be same, what will happen if I load the same class second time (from Fodler2)?
Try the jarcomp utility.
This is not directly answering your question, however you may get a look to the ASM framework . This allows you to analyze bytecode without having to load the classes with the class loader. It is probably easier to do it this way.

Categories

Resources