Compiling directories with JavaCompiler - java

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...)

Related

Java Compiler keeps saying "error while writing className: className.class (Permission denied)"

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

JavaCompiler with dependent class

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.

Java compiler project

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.

How to generate class file with java ASM?

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.

Add folder, which contains java sources, to classpath at runtime

Is it possible to add a folder which contains java source code as a classpath element. I have tried a few things and it seems that the classloadr is not picking up java soruce files? One of my attempts is shown below....
File uncompressedSrc = new File("uncompressed" + File.separator + "src" + File.separator);
URL uncompressedSrcURL = null;
try {
uncompressedSrcURL = new URL("file://"
+ uncompressedSrc.getAbsolutePath());
} catch (MalformedURLException e) {
e.printStackTrace();
}
URL elements[] = { uncompressedSrcURL };
new URLClassLoader(elements, ClassLoader.getSystemClassLoader());
I have found a solution to my problem... I used the following dirty "hack" to add a folder to class path...
public static void addUrl(URL u) {
URLClassLoader sysloader = (URLClassLoader) ClassLoader
.getSystemClassLoader();
Class<URLClassLoader> sysclass = URLClassLoader.class;
try {
Method method = sysclass.getDeclaredMethod("addURL", parameters);
method.setAccessible(true);
method.invoke(sysloader, new Object[] { u });
} catch (Throwable t) {
t.printStackTrace();
try {
throw new IOException(
"Error, could not add URL to system classloader");
} catch (IOException e) {
logger.log(Level.SEVERE, e.getMessage());
}
}
}
Java source code is nothing the JVM can handle by itself. Only compiled class files may be loaded by the classloader. So you may only add the content of JAR files or CLASS files to the classpath.
Sorry. I skimmed through your question way to fast.
As #Daniel says, the JVM can not read .java files, only .class files.
The java-files can be compiled into class-files and loaded in the JVM programatically as described here: Programmatically Compile and Execute with Java
The key ingredient is the following
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager filemanager = compiler.getStandardFileManager( null, null, null );
try {
Iterable compilationUnits = filemanager.getJavaFileObjects( file );
compiler.getTask( null, filemanager, null, null, null, compilationUnits ).call();
filemanager.close();
} catch ( IOException e ) {
e.printStackTrace();
}
Hope that helps!

Categories

Resources