Compile java files programmatically
Asked Answered
S

2

10

I know this has been asked and answered a lot but I still don't have a good solution and I still don't understand some parts. So I have the requirement to compile *.java files programmatically.

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

is what I'm using and (as expected) the compiler is null. Now, I do know that I have to use the JDK and not the JRE as a "runtime", but here is something I don't understand: isn't it enough to just put tools.jar in the classpath of the application and then have access to the Java Compiler API? If this is true, is there (I think there is) difference between a stand-alone java application and web-based application. Actually, I am trying to summon the JavaCompiler from a PlayFramework webapp so I figured out, maybe this solutions (with including the tools.jar) only works for stand-alone applications?

I also tried to create a custom ClassLoader and invoke the above mentioned method with reflection but all I am getting is null for the compiler object:

ClassLoader classloader = app.classloader();
File file = new File("lib/tools.jar");          
URL url = file.toURI().toURL();
URL[] urls = new URL[]{url};
ClassLoader newCL = new URLClassLoader(urls, classloader) {
};
Class<?> loadClass = newCL.loadClass("javax.tools.ToolProvider");
Method method = loadClass.getMethod("getSystemJavaCompiler", null);             
Object object = method.invoke(null);
System.out.println("Object: " + object); // NULL

Explanation for the code above:

  • I haven't included try/catches for the sake of simplicity.
  • app.classloader() is a Play-method which returns the ClassLoader of the App
  • tools.jar is included in my lib folder of the Play project (this implies that it is on the project's classpath - according to the Play documentation)

I am pretty sure there is something else I need to do before Play is able to load the Java Compiler class I just don't know what I am missing.

Options like Runtime.exec("javac myFile.java") and the Eclipse JDT compiler are known to me, but that's not what I am looking for.

Oh, and something like System.setProperty("java.home", "PATH_TO_YOUR_JDK"); and then ToolProvider.getSystemJavaCompiler(); is working but I find this solution so ugly.

Best regards

EDIT: (to give additional information and reflect the last status) This is a basic representation of web-app-structure:

myApp
|-conf\...
|-lib\MyJar.jar
|-lib\tools.jar
|-logs\...
|-...

MyJar.jar has now a META-INF/MANIFEST.MF file with the following content:

Manifest-Version: 1.0
Sealed: true
Main-Class: here.comes.my.main.class
Class-Path: tools.jar

Without starting the Play app, I am trying (in the lib folder): java -jar MyJar.jar - my simple main method tries to invoke the Compiler (ToolProvider.getSystemJavaCompiler();) and returns null. So this leads me to believe, that the problem has nothing to do with Play - I can't even get a Compiler when normally running my Jar!

Sniffle answered 31/3, 2013 at 13:53 Comment(0)
D
1

There is no difference between web applications and standalone applications.

You should not package the tools.jar into your webapp classpath. The classloader in Java generally only works bottom up. Thus the ToolProvider that is part of the jdk won't see your tools.jar in the webapp classpath (it cannot look down into the webapp classpath).

Solution: Use a JDK and make sure you point to it or put the tools.jar in an Java ext dir. You may override the ext dir by setting the property -Djava.ext.dir to any directory you like.

Dissected answered 18/4, 2013 at 13:18 Comment(1)
this sounds right. i would suggest put the tools jar in a common place or use the jdk.Thury
A
0

It says in the documentation that To run the Play framework, you need JDK 6 or later. How did you manage to run it with jre?

Anyways, if getSystemJavaCompiler returns null it means you do not have tools.jar in your classpath or it is corrupted. If it is Sun/Oracle java, make sure it has com.sun.tools.javac.api.JavacTool class in it.

And if you want to use custom class loader, it's the JavacTool class you need to load, not ToolProvider. Take a look at the way it does so in java 6:

        URL[] urls = {file.toURI().toURL()};
        ClassLoader cl = URLClassLoader.newInstance(urls);
        cl.setPackageAssertionStatus("com.sun.tools.javac", true);
        return Class.forName(defaultJavaCompilerName, false, cl);

where defaultJavaCompilerName = "com.sun.tools.javac.api.JavacTool"

Subclass it to JavaCompiler and get new instance - you'll have your compiler.

Acuff answered 31/3, 2013 at 18:21 Comment(3)
JAVA_HOME is set on my dev machine, returning the path to the JDK when asked for. But getting the "java.home" property out of running Play application (with System.getProperty(...)) returns the path to the JRE. So maybe that's the one problem. But there shouldn't be a problem when I have the tools.jar in my classpath. And I am pretty sure that tools.jar is in my classpath (checked it during the run of the web app) and that the file is not corrupted. I am going to try the above mentioned classloading stuff.Sniffle
I ran play on my ubuntu and I could get java compiler from scala action without any problems. Maybe there's something wrong with your setup?Acuff
Just tried JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler(); System.out.println("Java compiler: " + javaCompiler); from the onStart method in app/Global.java - with tools.jar in my app's classpath - even that is returning nullSniffle

© 2022 - 2024 — McMap. All rights reserved.