I'm making a project which will compile Java source files when ran.
The issue I'm having is that the StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null); line throws a NullPointerException.
Can anyone help fix this?
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(
diagnostics, null, null);
System.out.println("Searching for scripts...");
File[] javaFiles = src.listFiles(new FilenameFilter() {
public boolean accept(File src, String name) {
return name.toLowerCase().endsWith(".java");
}
});
Iterable<? extends JavaFileObject> compilationUnits = fileManager
.getJavaFileObjectsFromFiles(Arrays.asList(javaFiles));
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager,
diagnostics, null, null, compilationUnits);
From the JavaDoc of getSystemJavaCompiler():
Returns:
the compiler provided with this platform or null if no compiler is provided
The most probable cause of a non-existing system compiler is that you are executing the program in a JRE (Java Runtime Enviroment) which does not provide a compiler. Try using a JDK (Java Development Kit) environment instead.
Related
I'm working on a Tomcat WebApp for my university which enables students to compile their Java codes and see the trace. I'm installing it on a RHEL7 VM. But when I test the compilation function (this one is not implemented by me), the method I'm providing returns this:
error while writing className: className.class (Permission denied)
Error on line 1 in className.java
I'll show you the method I think is generating this:
public String compileJavaCode(String javaCode, String javaFileName, File workingDir) throws IOException, TimeoutException{
javax.tools.JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
this.createJavaFile(javaCode, javaFileName, workingDir);
JavaFileObject file = new JavaSourceFromString(javaFileName, javaCode);
Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(file);
compiler.getTask(null, fileManager, diagnostics, null, null, compilationUnits).call();
String diagn = "";
for ( Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()){
diagn+=diagnostic.getMessage(null)+"\n";//E.g. cannot find symbol symbol: variable variablename
diagn+="Error on line "+Long.toString(diagnostic.getLineNumber())+" in "+diagnostic.getSource().toUri();//E.g. Error on line 22 in ClassName.java
}
fileManager.close();
compiler.run(null, null, null, workingDir.getAbsolutePath()+File.separator+javaFileName);
return diagn;
}
Students will see the content of that diagn variable as a result for their code submission.
Fun fact is that I manage to get the className.class in the workingDir directory but I keep getting that error from the for cycle above. Could the problem be compiler.getTask(...).call()? I mean maybe compiler.run is able to generate the .class correctly but the compiler.getTask(...).call() is trying to write the .class somewhere else I don't have permission to write in.
P.S. This is a pretty legacy code so please be merciful with it. :)
As asked by #Alexander, this is the content of the Java file:
public class Sommatore {
public int somma(int i, int j) {
return i+j;
}
public int differenza(int i, int j) {
return i-j;
}
}
Seems like the user you are using doesn't have the permissions to write to the destination folder. What are the permissions of the workingDir?
Fun fact is that i manage to get the className.class in the workingDir directory but i keep getting that error from the for cycle above. Could the problem be the compiler.getTask(...).call()? I mean maybe compiler.run is able to generate the .class correctly but the compiler.getTask(...).call() is trying to write the .class somewhere else i don't have permission to write in.
In order to verify if this is true, you could create a folder with open permissions and try.
For example, you could try using as workingDir = /tmp and check what happens.
EDIT
I tried to replicate your code:
public class JavaCompiler {
public static void main(String args[]) throws IOException, TimeoutException {
File dir = new File(System.getProperty("user.dir") + "/src/main/java/");
System.out.println(compileJavaCode(dir));
}
public static String compileJavaCode(File workingDir) throws IOException, TimeoutException {
javax.tools.JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
// this.createJavaFile(javaCode, javaFileName, workingDir);
// JavaFileObject file = new JavaSourceFromString(javaFileName, javaCode);
Iterable<? extends JavaFileObject> compilationUnits = fileManager
.getJavaFileObjectsFromStrings(Arrays.asList(System.getProperty("user.dir") + "/src/main/java/Foo.java"));
// Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(file);
compiler.getTask(null, fileManager, diagnostics, null, null, compilationUnits).call();
String diagn = "";
for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {
diagn += diagnostic.getMessage(null) + "\n";//E.g. cannot find symbol symbol: variable variablename
diagn += "Error on line " + Long.toString(diagnostic.getLineNumber()) + " in " + diagnostic.getSource().toUri();//E.g. Error on line 22 in ClassName.java
}
fileManager.close();
compiler.run(null, null, null, workingDir.getAbsolutePath() + File.separator + "Foo.java");
return diagn;
}
}
with Foo.java
public class Foo {
public int somma(int i, int j) {
return i+j;
}
public int differenza(int i, int j) {
return i-j;
}
}
There are some changes, but the result should be the same.
I noticed that the "path" is specified in
File workingDir
that will be use in
compiler.run(null, null, null, workingDir.getAbsolutePath() + File.separator + "Foo.java");
and in
JavaFileObject file = new JavaSourceFromString(javaFileName, javaCode);
Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(file);
that in my example:
Iterable<? extends JavaFileObject> compilationUnits = fileManager
.getJavaFileObjectsFromStrings(Arrays.asList(System.getProperty("user.dir") + "/src/main/java/Foo.java"));
What contains yours
workingDir
and "file"?
JavaFileObject file = new JavaSourceFromString(javaFileName, javaCode);
In my case, are the same.
I tried to execute the code with different users, and if I use an user that isn't able to write in this folder I obtain
/tmp/testSO/src/main/java/Foo.java:5: error: error while writing Foo: /tmp/testSO/src/main/java/Foo.class (Permission denied)
public class Foo {
^
1 error
error while writing Foo: /tmp/testSO/src/main/java/Foo.class (Permission denied)
Error on line 5 in file:/tmp/testSO/src/main/java/Foo.java
I'm using JavaCompiler to compile a class. I have jar dependency, where I used to give it in class path, I have a class (class1) file in the same directly, which is a dependent for another class (class2).
Simply
Class1.class
Class2.java
I want to compile Class2.java, in Class2 have a code like
Class1.sayHi();
When I compile it its saying
error: cannot find symbol
How can I include Class1.class while compiling Class2
My compiler code
String fileToCompile = classFile;
System.setProperty("java.home", RuntimeCompiler.getJDKPath());
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
Iterable<? extends JavaFileObject> compilationSource =
fileManager.getJavaFileObjects(fileToCompile);
List<String> optionList = new ArrayList<String>();
optionList.addAll(Arrays.asList("-classpath",dynamicClassPath));
try{
compiler.getTask(null, null, null, optionList, null, compilationSource).call();
return true;
}catch (Exception e) {
return false;
}
You can specify multiple files/classes on the class path. Just separate them with either a colon or semicolon, depending on your platform.
Have you verified that it will compile using just the javac command? If this works then it must be something in your procedure and not the class path.
The class path for a particular compiled class can be given as
-classpath "full_folder_Path_Till_Package"
Ex:
dynamicClassPath = "C:/work/sample1/core"
in core directory you will have package folder "com" inside that dependant class.
I have the following which code which is being used to compile single files with JavaCompiler:
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromStrings(Arrays.asList(file.getAbsolutePath()));
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, null, null, compilationUnits);
boolean success = task.call();
fileManager.close();
My question is: How do I change this to compile all source files in a particular directory?
Get all the files from the directory (using directory.listFiles()), and pass the resulting array to getJavaFileObject(File...)
I have tried
ClassWriter t = new ClassWriter(0);
t.visitSource("testing.java", null);
t.visitEnd();
byte d[] = t.toByteArray();
FileOutputStream p = null;
try
{
p = new FileOutputStream("testing.class");
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
try
{
p.write(d);
}
catch (IOException e)
{
e.printStackTrace();
}
And the text within the testing.java is:
public class testing
{
public static void main(String args[])
{
System.out.println("Works!");
}
}
However, When I try to run the class file, it gives me this error:
Exception in thread "main" java.lang.UnsupportedClassVersionError: testing : Unsupported major.minor version 0.0
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631)
at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:283)
at java.net.URLClassLoader.access$000(URLClassLoader.java:58)
at java.net.URLClassLoader$1.run(URLClassLoader.java:197)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
How would I fix it so that the class file would output "Works!" to the console?
Edit:
I don't want it to have to access the jdk files! (<- Ex. javax.tools) That's why I was trying to get ASM to work.
Seems that you are trying to compile Java source file into a class file. That can be done with the Java compiler - the javac command line program or the tools in the javax.tools package.
ASM is for a different purpose. ASM can be used to create class files on-the-fly, without any source code. Read ASM's documentation to learn about Java bytecode and how to produce and read it with ASM.
Here is how a file is compiled using javax.tools package. Or then you could invoke the command line tools using Process. Check the documentation for additional arguments - what classpath to use, where to write the files etc.
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
public class TestingCompile {
public static void main(String[] args) {
JavaCompiler javac = ToolProvider.getSystemJavaCompiler();
int result = javac.run(null, null, null, "C:\\path\\to\\Testing.java");
if (result != 0) {
throw new RuntimeException("compile failed: exit " + result);
}
}
}
Here is how to create the same class file using ASM, without using the source file. I'm quite sure this is not what you want to be doing - otherwise you wouldn't have had to ask the question. ;)
This is just the output of ASMifierClassVisitor, so the bytes would still need to be written to a file or loaded dynamically into the class loader. I used the -debug argument so that ASMifier would show also the source file name and line numbers (the visitSource, visitLineNumber and visitLocalVariable calls are optional, so you could omit them and the related labels if the debug information is not needed).
import org.objectweb.asm.*;
public class TestingDump implements Opcodes {
public static byte[] dump() throws Exception {
ClassWriter cw = new ClassWriter(0);
FieldVisitor fv;
MethodVisitor mv;
AnnotationVisitor av0;
cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "Testing", null, "java/lang/Object", null);
cw.visitSource("Testing.java", null);
{
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(1, l0);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
mv.visitInsn(RETURN);
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLocalVariable("this", "LTesting;", null, l0, l1, 0);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
{
mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(3, l0);
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("Works!");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLineNumber(4, l1);
mv.visitInsn(RETURN);
Label l2 = new Label();
mv.visitLabel(l2);
mv.visitLocalVariable("args", "[Ljava/lang/String;", null, l0, l2, 0);
mv.visitMaxs(2, 1);
mv.visitEnd();
}
cw.visitEnd();
return cw.toByteArray();
}
}
I believe the problem is that the library expects a minimum set of certain methods will be called and I believe you don't have enough methods to have it generate a full class.
I suggest you use ASMifier to generate some templates.
You have corrupted the file in some way. There was no version 0.0
You can invoke the Java compiler from your source code to compile any Java source code to the class files. The compiler is written in Java itself and part of the standard jdk release. Try to look for a class called javac.
I'm not an expert in Java and I'm pretty new to the whole concept of compiling and running dynamic generated code, which is so simple in other languages, expecially script languages like Javascript and PHP.
I'm following this snippet of code:
http://www.java2s.com/Code/Java/JDK-6/CompilingfromMemory.htm
and I made something like this:
private final String = "GeneratedClass_" + Long.toHexString(random.nextLong());
private Method compileCode(String code) {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
if (compiler == null) return null;
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
JavaFileObject source = new JavaSource(className, code);
Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(source);
CompilationTask task = compiler.getTask(null, null, diagnostics, null, null, compilationUnits);
if (!task.call()) return null;
try {
return Class.forName(className).getDeclaredMethods()[0];
} catch (ClassNotFoundException e) {}
return null;
}
private class JavaSource extends SimpleJavaFileObject {
final String code;
JavaSource(String name, String code) {
super(URI.create("string:///" + name.replace('.','/') + Kind.SOURCE.extension),Kind.SOURCE);
this.code = code;
}
#Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {return code;}
}
Just imagine that the string code is something like
"public class GeneratedClass_65ce701239c32ce0 {
public String hello() {
return "Hello, world!";
}
}"
It works well until that Class.forName which throws a ClassNotFoundException. I'm puzzled since it doesn't seem I cut something important from the snippet: so, the class was compiled but where has it gone?
I read something about using a different class loader, but since, like I said, I'm pretty new to all this stuff I don't know where to head and how to use it, and how should I define my own extension of ClassLoader.
The only thing I know is that everything seems quite complicated to me...
Using Eclipse Indigo in Windows 7 and JDK 1.7.
One important thing you cut was all the error output and diagnostic information. You'd never know if something went wrong. However, everything looks correct. Your problem is most likely just that you didn't send any options to the compiler, so it'll write the class file out to wherever it feels like (current working directory is the default, I believe), and that's probably not on your classpath, especially in an IDE. Try running it from the command line to prove to yourself it works. This should work:
mkdir tmp
javac -d tmp <path your main class .java file>
java -cp .;tmp <your main class name>
If you're not familiar with the command-line tools, the argument to javac has to be a file system path to the .java file, and the argument to java needs to be the .-separated, fully-qualifed class name, like com.foo.Main. Doing that should:
Compile your class to the tmp directory.
Write your dynamically-generated class to the current directory.
Successfully load the newly compiled class from the current directory because it's on the classpath.