I have a simple .txt file which has pure Java code inside it like
public class C {
public static void main(String[] args ) {
System.out.println("This is executed");
}
}
The file is named C.txt. Now I want to write Java code that will read the code in C.txt and will compile and run the read code as a pure Java file. Note, I can easily rename C.txt to C.java and compile and run the code manually. However, this is not my intention. I want to read the .txt file as is and execute the code directly. Is this possible somehow?
You can use the javax.tools api form Java 6 to compile the code on the fly. However since your extension is illegal it will complain with a error: C.txt Class names are only accepted if annotation processing is explicitly requested.
To get around this (as mentioned in the comments) you must first load the code into a String and then execute it:
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.ToolProvider;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Iterator;
import java.util.NoSuchElementException;
public class MyCompiler2 {
public static void main(String[] args) throws Exception {
String program = "";
try {
BufferedReader in = new BufferedReader(new FileReader("C.txt"));
String str;
while ((str = in.readLine()) != null) {
program += str;
}
in.close();
} catch (IOException e) {
}
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
Iterable<? extends JavaFileObject> fileObjects;
fileObjects = getJavaSourceFromString(program);
compiler.getTask(null, null, null, null, null, fileObjects).call();
Class<?> clazz = Class.forName("C");
Method m = clazz.getMethod("main", new Class[]{String[].class});
Object[] _args = new Object[]{new String[0]};
m.invoke(null, _args);
}
static Iterable<JavaSourceFromString> getJavaSourceFromString(String code) {
final JavaSourceFromString jsfs;
jsfs = new JavaSourceFromString("code", code);
return new Iterable<JavaSourceFromString>() {
public Iterator<JavaSourceFromString> iterator() {
return new Iterator<JavaSourceFromString>() {
boolean isNext = true;
public boolean hasNext() {
return isNext;
}
public JavaSourceFromString next() {
if (!isNext)
throw new NoSuchElementException();
isNext = false;
return jsfs;
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
};
}
}
class JavaSourceFromString extends SimpleJavaFileObject {
final String code;
JavaSourceFromString(String name, String code) {
super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
this.code = code;
}
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return code;
}
}
Notice how you need to explicitly provide the method and class name in order for reflection to execute your code.
I think I'd start with BeanShell, which allows you to compile and execute Java source held in a string.
Check out this thread for how to start the compile from within Java...
How to set the source for compilation by a CompilationTask
Related
This question already has answers here:
Compile code fully in memory with javax.tools.JavaCompiler [duplicate]
(7 answers)
Closed 6 years ago.
I want to treat a String as a Java file then compile and run it. In other words, use Java as a script language.
To get better performance, we should avoid writing .class files to disk.
This answer is from one of my blogs, Compile and Run Java Source Code in Memory.
Here are the three source code files.
MemoryJavaCompiler.java
package me.soulmachine.compiler;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.tools.*;
/**
* Simple interface to Java compiler using JSR 199 Compiler API.
*/
public class MemoryJavaCompiler {
private javax.tools.JavaCompiler tool;
private StandardJavaFileManager stdManager;
public MemoryJavaCompiler() {
tool = ToolProvider.getSystemJavaCompiler();
if (tool == null) {
throw new RuntimeException("Could not get Java compiler. Please, ensure that JDK is used instead of JRE.");
}
stdManager = tool.getStandardFileManager(null, null, null);
}
/**
* Compile a single static method.
*/
public Method compileStaticMethod(final String methodName, final String className,
final String source)
throws ClassNotFoundException {
final Map<String, byte[]> classBytes = compile(className + ".java", source);
final MemoryClassLoader classLoader = new MemoryClassLoader(classBytes);
final Class clazz = classLoader.loadClass(className);
final Method[] methods = clazz.getDeclaredMethods();
for (final Method method : methods) {
if (method.getName().equals(methodName)) {
if (!method.isAccessible()) method.setAccessible(true);
return method;
}
}
throw new NoSuchMethodError(methodName);
}
public Map<String, byte[]> compile(String fileName, String source) {
return compile(fileName, source, new PrintWriter(System.err), null, null);
}
/**
* compile given String source and return bytecodes as a Map.
*
* #param fileName source fileName to be used for error messages etc.
* #param source Java source as String
* #param err error writer where diagnostic messages are written
* #param sourcePath location of additional .java source files
* #param classPath location of additional .class files
*/
private Map<String, byte[]> compile(String fileName, String source,
Writer err, String sourcePath, String classPath) {
// to collect errors, warnings etc.
DiagnosticCollector<JavaFileObject> diagnostics =
new DiagnosticCollector<JavaFileObject>();
// create a new memory JavaFileManager
MemoryJavaFileManager fileManager = new MemoryJavaFileManager(stdManager);
// prepare the compilation unit
List<JavaFileObject> compUnits = new ArrayList<JavaFileObject>(1);
compUnits.add(fileManager.makeStringSource(fileName, source));
return compile(compUnits, fileManager, err, sourcePath, classPath);
}
private Map<String, byte[]> compile(final List<JavaFileObject> compUnits,
final MemoryJavaFileManager fileManager,
Writer err, String sourcePath, String classPath) {
// to collect errors, warnings etc.
DiagnosticCollector<JavaFileObject> diagnostics =
new DiagnosticCollector<JavaFileObject>();
// javac options
List<String> options = new ArrayList<String>();
options.add("-Xlint:all");
// options.add("-g:none");
options.add("-deprecation");
if (sourcePath != null) {
options.add("-sourcepath");
options.add(sourcePath);
}
if (classPath != null) {
options.add("-classpath");
options.add(classPath);
}
// create a compilation task
javax.tools.JavaCompiler.CompilationTask task =
tool.getTask(err, fileManager, diagnostics,
options, null, compUnits);
if (task.call() == false) {
PrintWriter perr = new PrintWriter(err);
for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
perr.println(diagnostic);
}
perr.flush();
return null;
}
Map<String, byte[]> classBytes = fileManager.getClassBytes();
try {
fileManager.close();
} catch (IOException exp) {
}
return classBytes;
}
}
MemoryJavaFileManager.java
package me.soulmachine.compiler;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.nio.CharBuffer;
import java.util.HashMap;
import java.util.Map;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.SimpleJavaFileObject;
/**
* JavaFileManager that keeps compiled .class bytes in memory.
*/
#SuppressWarnings("unchecked")
final class MemoryJavaFileManager extends ForwardingJavaFileManager {
/** Java source file extension. */
private final static String EXT = ".java";
private Map<String, byte[]> classBytes;
public MemoryJavaFileManager(JavaFileManager fileManager) {
super(fileManager);
classBytes = new HashMap<>();
}
public Map<String, byte[]> getClassBytes() {
return classBytes;
}
public void close() throws IOException {
classBytes = null;
}
public void flush() throws IOException {
}
/**
* A file object used to represent Java source coming from a string.
*/
private static class StringInputBuffer extends SimpleJavaFileObject {
final String code;
StringInputBuffer(String fileName, String code) {
super(toURI(fileName), Kind.SOURCE);
this.code = code;
}
public CharBuffer getCharContent(boolean ignoreEncodingErrors) {
return CharBuffer.wrap(code);
}
}
/**
* A file object that stores Java bytecode into the classBytes map.
*/
private class ClassOutputBuffer extends SimpleJavaFileObject {
private String name;
ClassOutputBuffer(String name) {
super(toURI(name), Kind.CLASS);
this.name = name;
}
public OutputStream openOutputStream() {
return new FilterOutputStream(new ByteArrayOutputStream()) {
public void close() throws IOException {
out.close();
ByteArrayOutputStream bos = (ByteArrayOutputStream)out;
classBytes.put(name, bos.toByteArray());
}
};
}
}
public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location,
String className,
Kind kind,
FileObject sibling) throws IOException {
if (kind == Kind.CLASS) {
return new ClassOutputBuffer(className);
} else {
return super.getJavaFileForOutput(location, className, kind, sibling);
}
}
static JavaFileObject makeStringSource(String fileName, String code) {
return new StringInputBuffer(fileName, code);
}
static URI toURI(String name) {
File file = new File(name);
if (file.exists()) {
return file.toURI();
} else {
try {
final StringBuilder newUri = new StringBuilder();
newUri.append("mfm:///");
newUri.append(name.replace('.', '/'));
if(name.endsWith(EXT)) newUri.replace(newUri.length() - EXT.length(), newUri.length(), EXT);
return URI.create(newUri.toString());
} catch (Exception exp) {
return URI.create("mfm:///com/sun/script/java/java_source");
}
}
}
}
MemoryClassLoader.java
package me.soulmachine.compiler;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
/**
* ClassLoader that loads .class bytes from memory.
*/
final class MemoryClassLoader extends URLClassLoader {
private Map<String, byte[]> classBytes;
public MemoryClassLoader(Map<String, byte[]> classBytes,
String classPath, ClassLoader parent) {
super(toURLs(classPath), parent);
this.classBytes = classBytes;
}
public MemoryClassLoader(Map<String, byte[]> classBytes, String classPath) {
this(classBytes, classPath, ClassLoader.getSystemClassLoader());
}
public MemoryClassLoader(Map<String, byte[]> classBytes) {
this(classBytes, null, ClassLoader.getSystemClassLoader());
}
public Class load(String className) throws ClassNotFoundException {
return loadClass(className);
}
public Iterable<Class> loadAll() throws ClassNotFoundException {
List<Class> classes = new ArrayList<Class>(classBytes.size());
for (String name : classBytes.keySet()) {
classes.add(loadClass(name));
}
return classes;
}
protected Class findClass(String className) throws ClassNotFoundException {
byte[] buf = classBytes.get(className);
if (buf != null) {
// clear the bytes in map -- we don't need it anymore
classBytes.put(className, null);
return defineClass(className, buf, 0, buf.length);
} else {
return super.findClass(className);
}
}
private static URL[] toURLs(String classPath) {
if (classPath == null) {
return new URL[0];
}
List<URL> list = new ArrayList<URL>();
StringTokenizer st = new StringTokenizer(classPath, File.pathSeparator);
while (st.hasMoreTokens()) {
String token = st.nextToken();
File file = new File(token);
if (file.exists()) {
try {
list.add(file.toURI().toURL());
} catch (MalformedURLException mue) {}
} else {
try {
list.add(new URL(token));
} catch (MalformedURLException mue) {}
}
}
URL[] res = new URL[list.size()];
list.toArray(res);
return res;
}
}
Explanations:
In order to represent a Java source file in memory instead of disk, I defined a StringInputBuffer class in the MemoryJavaFileManager.java.
To save the compiled .class files in memory, I implemented a class MemoryJavaFileManager. The main idea is to override the function getJavaFileForOutput() to store bytecodes into a map.
To load the bytecodes in memory, I have to implement a customized classloader MemoryClassLoader, which reads bytecodes in the map and turn them into classes.
Here is a unite test.
package me.soulmachine.compiler;
import org.junit.Test;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import static org.junit.Assert.assertEquals;
public class MemoryJavaCompilerTest {
private final static MemoryJavaCompiler compiler = new MemoryJavaCompiler();
#Test public void compileStaticMethodTest()
throws ClassNotFoundException, InvocationTargetException, IllegalAccessException {
final String source = "public final class Solution {\n"
+ "public static String greeting(String name) {\n"
+ "\treturn \"Hello \" + name;\n" + "}\n}\n";
final Method greeting = compiler.compileStaticMethod("greeting", "Solution", source);
final Object result = greeting.invoke(null, "soulmachine");
assertEquals("Hello soulmachine", result.toString());
}
}
Reference
JavaCompiler.java from Cloudera Morphlines
How to create an object from a string in Java (how to eval a string)?
InMemoryJavaCompiler
Java-Runtime-Compiler
动态的Java - 无废话JavaCompilerAPI中文指南
I have to run a run a python script from a maven project. I created a temporary class with main method to check if it works as expected, used the process builder and it works if I specify the absolute path of the python script and then run the java class from eclipse using RUN as Java application.
If I change it getClass().getResourceAsStream("/scripts/script.py"), it throws an exception as it cannot locate the python script.
What would be the best place to place the python script and how can I access it in the Java class without specifying the complete path. Since I am new to maven, it could be due to the method used to execute the Java program.
package discourse.apps.features;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
public class Test {
protected String scriptPath = "/Users/user1/project1/scripts/script.py";
protected String python3Path = "/Users/user1/.virtualenvs/python3/bin/python3";
public static void main(String[] args) throws IOException {
new Test().score();
}
public JSONObject score() {
String text1="a";
String text2="b";
JSONObject rmap =null;
try
{
String line= null;
String writedir=System.getProperty("user.dir")+ "/Tmp";
String pbCommand[] = { python3Path, scriptPath,"--stringa", text1, "--stringb",text2,"--writedir", writedir };
ProcessBuilder pb = new ProcessBuilder(pbCommand);
Process p = pb.start();
InputStream is = p.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
while ((line = br.readLine()) != null) {
JSONParser parser = new JSONParser();
rmap= (JSONObject) parser.parse(line);
}
} catch (IOException | ParseException ioe) {
System.err.println("Error running script");
ioe.printStackTrace();
System.exit(0);
}
return rmap;
}
}
Here is the output from pb command
pbCommand[0]:/Users/user1/.virtualenvs/python3/bin/python3
pbCommand[1]:displays the complete python script
import os,sys
from pyrouge import Rouge155
import json
from optparse import OptionParser
def get_opts():
parser = OptionParser()
parser.add_option("--stringa", dest="str_a",help="First string")
parser.add_option("--stringb", dest= "str_b",help="second string")
parser.add_option("--writedir", dest="write_dir", help="Tmp write directory for rouge")
(options, args) = parser.parse_args()
if options.str_a is None:
print("Error: requires string")
parser.print_help()
sys.exit(-1)
if options.str_b is None:
print("Error:requires string")
parser.print_help()
sys.exit(-1)
if options.write_dir is None:
print("Error:requires write directory for rouge")
parser.print_help()
sys.exit(-1)
return (options, args)
def readTextFile(Filename):
f = open(Filename, "r", encoding='utf-8')
TextLines=f.readlines()
f.close()
return TextLines
def writeTextFile(Filename,Lines):
f = open(Filename, "w",encoding='utf-8')
f.writelines(Lines)
f.close()
def rougue(stringa, stringb, writedirRouge):
newrow={}
r = Rouge155()
count=0
dirname_sys= writedirRouge +"rougue/System/"
dirname_mod=writedirRouge +"rougue/Model/"
if not os.path.exists(dirname_sys):
os.makedirs(dirname_sys)
if not os.path.exists(dirname_mod):
os.makedirs(dirname_mod)
Filename=dirname_sys +"string_."+str(count)+".txt"
LinesA=list()
LinesA.append(stringa)
writeTextFile(Filename, LinesA)
LinesB=list()
LinesB.append(stringb)
Filename=dirname_mod+"string_.A."+str(count)+ ".txt"
writeTextFile(Filename, LinesB)
r.system_dir = dirname_sys
r.model_dir = dirname_mod
r.system_filename_pattern = 'string_.(\d+).txt'
r.model_filename_pattern = 'string_.[A-Z].#ID#.txt'
output = r.convert_and_evaluate()
output_dict = r.output_to_dict(output)
newrow["rouge_1_f_score"]=output_dict["rouge_1_f_score"]
newrow["rouge_2_f_score"]=output_dict["rouge_2_f_score"]
newrow["rouge_3_f_score"]=output_dict["rouge_3_f_score"]
newrow["rouge_4_f_score"]=output_dict["rouge_4_f_score"]
newrow["rouge_l_f_score"]=output_dict["rouge_l_f_score"]
newrow["rouge_s*_f_score"]=output_dict["rouge_s*_f_score"]
newrow["rouge_su*_f_score"]=output_dict["rouge_su*_f_score"]
newrow["rouge_w_1.2_f_score"]=output_dict["rouge_w_1.2_f_score"]
rouge_dict=json.dumps(newrow)
print (rouge_dict)
def run():
(options, args) = get_opts()
stringa=options.str_a
stringb=options.str_b
writedir=options.write_dir
rougue(stringa, stringb, writedir)
if __name__ == '__main__':
run()
pbCommand[2]:--stringa
pbCommand[3]:a
pbCommand[4]:--stringb
pbCommand[5]:b
pbCommand[6]:--writedir
pbCommand[7]:/users/user1/project1/Tmp
Put the script in the main/resources folder it will then be copied to the target folder.
Then make sure you use something like the com.google.common.io.Resources class, which you can add with
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava-io</artifactId>
<version>r03</version>
</dependency>
I then have a class like this which helps to convert resource files to Strings:
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import com.google.common.base.Charsets;
import com.google.common.io.Resources;
public class FileUtil
{
public static String convertResourceToString(URL url)
{
try
{
return Resources.toString(url, Charsets.UTF_8);
}
catch (Exception e)
{
return null;
}
}
public static String convertResourceToString(String path)
{
return convertResourceToString(Resources.getResource(path));
}
public static String convertResourceToString(URI url)
{
try
{
return convertResourceToString(url.toURL());
}
catch (MalformedURLException e)
{
return null;
}
}
}
Some advice if you are learning maven try using it instead of the IDE to run and package your application, that is what it is suppose to do. Then once you are confident that the application will function as a packaged jar then just use the IDE to run it.
I wanted to create a web page in Struts2.0 which contains a textarea, a property field and submit button. User will enter a java code in this text area and my code will compile it and execute it and will give the result of this code on my property field... the above code works fine in a standalone application. but it does not shows any thing in my web application. plz any one can address it... thanks in advance.
package org.controller;
import com.opensymphony.xwork2.ActionSupport;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
public class JCompilerAction extends ActionSupport
{
String program;
String MSg;
public JCompilerAction() {
}
public String getProgram() {
return program;
}
public void setProgram(String program) {
this.program = program;
}
public String getMSg() {
return MSg;
}
public void setMSg(String MSg) {
this.MSg = MSg;
}
public String Compile() {
try {
byte[] bFile = program.getBytes();
File f = new File("D:/nullprog.java");
FileOutputStream fileOuputStream = new FileOutputStream(f);
fileOuputStream.write(bFile);
fileOuputStream.close();
Process p1 = Runtime.getRuntime().exec("javac D:/nullprog.java");
BufferedReader in = new BufferedReader(new InputStreamReader(p1.getErrorStream()));
String line = null;
boolean isError = false;
while ((line = in.readLine()) != null) {
MSg = line;
isError = true;
return SUCCESS;
}
p1.waitFor();
if (!isError)
{
Process p2 = Runtime.getRuntime().exec("cmd /c start nullprog");
BufferedReader in1 = new BufferedReader(new InputStreamReader(p2.getInputStream()));
String line1 = null;
while ((line1 = in1.readLine()) != null) {
MSg += line1;
}
return SUCCESS;
}
} catch (Exception e) {
e.printStackTrace();
}
return SUCCESS;
}
public String execute() {
return SUCCESS;
}
}
Below is a simple program that first compiles a code and then executes it:
Executer.java
import java.lang.reflect.Method;
public class Executer {
public static void main(String args[]) {
try{
java.lang.Process p1 = Runtime.getRuntime().exec("javac MyClass.java");
p1.waitFor();
Class<?> c = Class.forName("MyClass");
Object obj = c.newInstance();
Method[] mArr = obj.getClass().getMethods();
for(Method m : mArr){
if(m.getName().equalsIgnoreCase("main")){
m.invoke(obj, new Object[]{new String[]{}});
}
}
} catch (Exception e){
e.printStackTrace();
}
}
}
MyClass.java
import javax.swing.JOptionPane;
public class MyClass {
public static void main(String[] args) {
JOptionPane.showMessageDialog(null, "Hii");
}
}
For simplicity both the classes are in same package. When I execute Executer class, it first compiles MyClass.java and then runs the main method.
If your requirement is to have the Java files in some folder outside of the project, the to load the compiler class file, you need to follow as mentioned in this answer.
I have created program which loads a java(JPanel) file which user chooses.User basically chooses a Java file which gets compiled by JavaCompiler and next generated class file is loaded.
But problem is coming when any changes are done in the java file(JPanel) through some text editor ,since any new changes are not reflected in the class file even after closing the program and re-running the project.
I think same class file is loaded again and again from memory.
is there any way to clear the loaded class from memory?
Compilation:
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
if (compiler != null) {
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
StandardJavaFileManager stdFileManager = compiler.getStandardFileManager(diagnostics, null, null);
Iterable<? extends JavaFileObject> fileObjects = stdFileManager.getJavaFileObjectsFromFiles(filesToCompile);
List<String> optionList = new ArrayList<String>();
// set compiler's classpath to be same as the runtime's
rootDir=Utility.createRootDir();
optionList.addAll(Arrays.asList("-d", rootDir.getAbsolutePath(), "-classpath", System.getProperty("java.class.path")));
// optionList.add(()
try {
stdFileManager.flush();
} catch (IOException e1) {
e1.printStackTrace();
}
CompilationTask task = compiler.getTask(null, stdFileManager,null, optionList, null, fileObjects);
Boolean result = task.call();
try {
stdFileManager.flush();
stdFileManager.close();
} catch (IOException e) {
e.printStackTrace();
}
}
Loading:
loader = new URLClassLoader(new URL[] { rootDir.toURI().toURL() });
cls = Class.forName(Utility.extractFQDN(sourceFile)+"."+Utility.extractClassName(sourceFile),true, loader);
panel= (JPanel) cls.newInstance();
I have checked the compiled class file with decompiler it has updated code but I don't know why previous class file is being loaded from memory by class loader.
Edit:
Here's an SSCCE compiling strings repeatedly to the same class name and demonstrating new behavior. To avoid the whole mess with files it does everything in memory. I think this should be easily adaptable to your application.
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.SimpleJavaFileObject;
import javax.tools.ToolProvider;
public class Compile {
static class OutFile extends SimpleJavaFileObject {
private final ByteArrayOutputStream out = new ByteArrayOutputStream();
OutFile(String name) {
super(URI.create("memory:///" + name.replace('.','/') + Kind.CLASS.extension), Kind.CLASS);
}
#Override
public OutputStream openOutputStream() throws IOException {
return out;
}
}
static class InFile extends SimpleJavaFileObject {
final String code;
InFile(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;
}
}
static class Loader extends ClassLoader {
private final Map<String, OutFile> files = new HashMap<String, OutFile>();
public OutFile add(String className) {
OutFile file = new OutFile(className);
files.put(className, file);
return file;
}
#Override
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class<?> c = findLoadedClass(name);
if(c == null) {
OutFile file = files.get(name);
if(file == null) {
return super.loadClass(name, resolve);
}
c = defineClass(name, file.out.toByteArray(), 0, file.out.size());
}
if(resolve) {
resolveClass(c);
}
return c;
}
}
static class FileManager extends ForwardingJavaFileManager<JavaFileManager> {
private final Loader loader = new Loader();
protected FileManager(JavaFileManager fileManager) {
super(fileManager);
}
public Class<?> getClass(String name) throws ClassNotFoundException {
return loader.loadClass(name);
}
#Override
public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException {
return loader.add(className);
}
}
public static void compileAndRun(String source) throws Exception {
InFile in = new InFile("Main", "class Main {\npublic static void main(String[] args) {\n" + source + "\n}\n}");
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
FileManager manager = new FileManager(compiler.getStandardFileManager(null, null, null));
compiler.getTask(null, manager, null, null, null, Collections.singletonList(in)).call();
Method method = manager.getClass("Main").getMethod("main", String[].class);
method.setAccessible(true);
method.invoke(null, (Object)new String[0]);
}
public static void main(String[] args) throws Exception {
compileAndRun("System.out.println(\"Hello\");");
compileAndRun("System.out.println(\"World\");");
}
}
Original:
ClassLoader (and subclasses like URLClassLoader) will always ask the parent class loader to load the class, if there is a parent. If you don't explicitly set a parent when constructing it, the parent is set to the system class loader. So, all the new class loaders you create are deferring back to the system class loader, which already has the class defined.
To get the behavior that you want, set the parent to null:
loader = new URLClassLoader(new URL[] { rootDir.toURI().toURL() }, null);
Edit: Note that is only a problem because you are compiling your classes to the root directory, which is also on the classpath of the system class loader. If you compiled to some temp directory, the system class loader would not be able to find the class, and the URL loader would load the class itself.
I follow the tutorial from Generating Java classes dynamically through Java compiler API, the code is work but what I see is the program will create a class file after compiling it.
import java.io.IOException;
import java.net.URI;
import java.util.Arrays;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.*;
public class Compiler {
static final Logger logger = Logger.getLogger(Compiler.class.getName());
static String sourceCode = "class HelloWorld{"
+ "public static void main (String args[]){"
+ "System.out.println (\"Hello, dynamic compilation world!\");"
+ "}"
+ "}";
public void doCompilation() {
SimpleJavaFileObject fileObject = new DynamicJavaSourceCodeObject("HelloWorld", sourceCode);
JavaFileObject javaFileObjects[] = new JavaFileObject[]{fileObject};
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager stdFileManager = compiler.getStandardFileManager(null, Locale.getDefault(), null);
Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(javaFileObjects);
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
CompilationTask compilerTask = compiler.getTask(null, stdFileManager, diagnostics, null, null, compilationUnits);
boolean status = compilerTask.call();
if (!status) {
for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
System.out.format("Error on line %d in %s\n", diagnostic.getLineNumber(), diagnostic);
}
}
try {
stdFileManager.close();
} catch (IOException ex) {
Logger.getLogger(Compiler.class.getName()).log(Level.SEVERE, null, ex);
}
}
public static void main(String args[]) {
new Compiler().doCompilation();
}
}
class DynamicJavaSourceCodeObject extends SimpleJavaFileObject {
private String qualifiedName;
private String sourceCode;
protected DynamicJavaSourceCodeObject(String name, String code) {
super(URI.create("string:///" + name.replaceAll("\\.", "/") + JavaFileObject.Kind.SOURCE.extension), JavaFileObject.Kind.SOURCE);
this.qualifiedName = name;
this.sourceCode = code;
}
#Override
public CharSequence getCharContent(boolean ignoreEncodingErrors)
throws IOException {
return sourceCode;
}
public String getQualifiedName() {
return qualifiedName;
}
public void setQualifiedName(String qualifiedName) {
this.qualifiedName = qualifiedName;
}
public String getSourceCode() {
return sourceCode;
}
public void setSourceCode(String sourceCode) {
this.sourceCode = sourceCode;
}
}
Is it possible that after call compilerTask.call(); to not create a class file? If yes how to do that?
For what your doing, I would use Janino. It appears doable using just the JavaCompiler, but not well documented. See the comment I added withe linked question for an example of going about it with the JavaCompiler.
EDIT:
I found an easy to understand example using the JavaCompiler.
To avoid creation of class file by the JavaCompiler use the argument: "-proc:only"