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.
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 have a java application which was developed in Eclipse.
There is a folder on the system which contains a lot of ".java" files. These ".java" files are classes which some user has written. I wish to load all these java classes and compile them inside my java application.
Another property of all the ".java" files are that all the classes written inside extend a class which is inside my original application.
I used the following to read and compile all the classes.
File parentFile = new File(rulesDir + "\\");
String fileName = rulesDir + "\\" + ruleName + ".java";
File ruleFile = new File(fileName);
// Compile source file.
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
compiler.run(null, null, null, ruleFile.getPath());
// Load and instantiate compiled class.
URLClassLoader classLoader = URLClassLoader.newInstance(new URL[] { parentFile.toURI().toURL() });
Class<? extends AbstractRule> cls = (Class<? extends AbstractRule>)Class.forName(ruleName, true, classLoader);
If I run the above code inside Eclipse, it works fine. When I run the application as a jar from elsewhere, it throws an ClassNotFoundException for the line
Class<? extends AbstractRule> cls = (Class<? extends AbstractRule>)Class.forName(ruleName, true, classLoader);
Why is this happening? What is different that it executes in Eclipse and doesn't via command line?
From the documentation for Class.forName
name - fully qualified name of the desired class
So, in order to get that fully qualified class name, you need to manipulate your rulesDir variable to replace the backslashes with periods, then prepend that to your ruleName variable, combined with another period, to get the fully qualified class name. Then you'll be able to use the ClassLoader to load the class. The fully qualified name is required so that the ClassLoader can find your resource from the root of your classpath.
NB I make the assumption that your rulesDir is a relative path from the base of your classpath. If it is not, then you'll have extra manipulation to do here
See code manipulation below:
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
import rules.AbstractRule;
public class MainApp {
public static void main(String[] args) {
try {
System.out.println("Test");
// NB Amended here to now take project root, relative path to rules directory and class name. So that compilation can take place based on the absolute path and class loading from the relative one.
compile("C:\\Media\\Code\\manodestra_java\\src\\tmp", "rules", "TestRule");
} catch (Exception e) {
e.printStackTrace();
}
}
private static void compile(String projectRoot, String rulesDir, String ruleName) throws Exception {
File parentFile = new File(projectRoot + "\\" + rulesDir + "\\");
System.out.println(parentFile);
String fileName = parentFile.getCanonicalPath() + "\\" + ruleName + ".java";
File ruleFile = new File(fileName);
System.out.println(ruleFile);
// Compile source file.
System.out.println("Compiling...");
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
compiler.run(null, null, null, ruleFile.getPath());
// Load and instantiate compiled class.
System.out.println("Loading class...");
URLClassLoader classLoader = URLClassLoader.newInstance(new URL[] { parentFile.toURI().toURL() });
System.out.println("Class Loader: " + classLoader);
ruleName = rulesDir.replace("\\", ".") + "." + ruleName;
Class<? extends AbstractRule> clazz = (Class<? extends AbstractRule>)Class.forName(ruleName, true, classLoader);
System.out.println(clazz);
}
}
For the sake of my testing, this class was defined in the default package and I created a rules directory below that level to contain my subclasses of AbstractRule. So, rules.TestRule was my fully qualified path to my class name. But, yours could be...
com.example.testapplication.rules.TestRule, etc.
And that's what would be required in Class.forName. There's a path to your classpath root, then the relative path from there to your java files (which is equivalent to the package of your classes), then the actual class names under that package path.
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.
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'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.