動態編譯-線上Java編譯執行例子
首先簡單介紹一下思路:
1.得到java程式的原始碼,不需要匯入包。得到原始碼之後在前面加入”import java.util.*;”
2.通過JavaCompiler物件可以幫助我們將java原始碼編譯成class檔案。
3.通過DiagnosticCollector物件可以獲得編譯過程中產生的編譯資訊。
4.通過StandardJavaFileManager物件管理生成的class檔案,例如檔案的存放位置。
5.StringSourceJavaObject物件可以對java原始碼進行包裝並處理。
資料是控制檯輸入的,所以要重定向System.in(注意儲存標準的輸入流);另外程式的輸出是到標準的輸出流的,為了獲得輸出結果,我的方法是重定向輸出流到ByteArrayOutputStream,然後利用ByteArrayOutputStream構造BufferedReader。
6.執行程式,通過java的反射機制,獲得main函式的Method物件。
7.執行時間的計算: 通過System.currentTimeMillis()方法。
8.程式所需記憶體: 通過Runtime的freeMemory()方法。
9.異常資訊的獲取:StringWriter sw = new StringWriter(); e.printStackTrace(new PrintWriter(sw, true)); sw.toString();
遇到的問題:
1. JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 出現NullPointerException。
檢視部分原始碼如下:
private static final String[] defaultToolsLocation = { "lib", "tools.jar" }; private Class<?> findSystemToolClass(String toolClassName) throws MalformedURLException, ClassNotFoundException { // try loading class directly, in case tool is on the bootclasspath try { return Class.forName(toolClassName, false, null); } catch (ClassNotFoundException e) { trace(FINE, e); // if tool not on bootclasspath, look in default tools location (tools.jar) ClassLoader cl = (refToolClassLoader == null ? null : refToolClassLoader.get()); if (cl == null) { File file = new File(System.getProperty("java.home")); if (file.getName().equalsIgnoreCase("jre")) file = file.getParentFile(); for (String name : defaultToolsLocation) file = new File(file, name); // if tools not found, no point in trying a URLClassLoader // so rethrow the original exception. if (!file.exists()) throw e; URL[] urls = { file.toURI().toURL() }; trace(FINE, urls[0].toString()); cl = URLClassLoader.newInstance(urls); refToolClassLoader = new WeakReference<ClassLoader>(cl); } return Class.forName(toolClassName, false, cl); } }
列印 System.out.println(System.getProperty("java.home")); 如下:
C:\Program Files (x86)\Java\jre6
defaultToolsLocation = { "lib", "tools.jar" }; 也就是最終到
C:\Program Files (x86)\Java\jre6\lib\tools.jar中尋找tools.jar
然而jre6\lib中沒有tools.jar, 而是在C:\Program Files (x86)\Java\jdk\lib中。最直接的辦法就是將它複製進去就行了。
2.異常資訊的獲取。
3.輸入流和輸出流的重定向。
詳細內容請看程式碼!
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
public class CompileAndRunJavaFile {
public static void main(String[] args) {
StringBuilder code = new StringBuilder();
try {
BufferedReader br = new BufferedReader(new FileReader(new File("測試程式地址")));
String content;
while((content = br.readLine()) != null){
code.append(content).append("\n");
}
} catch (Exception e) {
e.printStackTrace();
}
CompileAndRunJavaFile cr = new CompileAndRunJavaFile();
cr.compileAndRunJavaFile(code.toString());
if(cr.isCompileAndRunOK()) {
System.out.println("執行時間: " + cr.getUseTime() + "ms");
System.out.println("記憶體使用: " + cr.getUseMemory() + "kb9");
System.out.println("執行結果: \n" + cr.getOutMsg());
} else if(cr.isCompilerError()) {
System.out.println("編譯錯誤: " + cr.getCE());
} else if(cr.isRunningError()) {
System.out.println("執行錯誤: " + cr.getError());
}
}
//編譯錯誤
private StringBuilder ce = new StringBuilder();
public String getCE(){
return ce.toString();
}
//記憶體使用
private double useMemory = 0.0;
public double getUseMemory(){
return useMemory;
}
//執行時間
private long useTime = 0;
public long getUseTime(){
return useTime;
}
//輸出資訊
private StringBuilder outMsg = new StringBuilder();
public String getOutMsg(){
return outMsg.toString();
}
//異常資訊
private String error = null;
public String getError(){
return error;
}
//是否正常編譯並執行
private boolean isCompileAndRunOK = false;
public boolean isCompileAndRunOK(){
return isCompileAndRunOK;
}
//程式的執行時間, 單位:ms
private int limitTime = 2000;
//程式所佔記憶體, 單位 :KB
private double limitMemory = 256000.0;
public void setLimitTime(int limitTime){
this.limitTime = limitTime;
}
public void setLimitMemory(double limitMemory){
this.limitMemory = limitMemory;
}
//是否為編譯錯誤
private boolean isCompilerError = false;
public boolean isCompilerError(){
return isCompilerError;
}
//是否為執行錯誤
private boolean isRunningError = false;
public boolean isRunningError(){
return isRunningError;
}
private static final String className = "Main";
private static final String methodName = "main";
private String getClassOutput(){
//設定class檔案的存放位置
if(System.getProperty("java.class.path").contains("bin")) return "bin/";
else return "./";
}
private void compileAndRunJavaFile(String code){
PrintStream ps = null;
FileInputStream fis = null;
BufferedReader br = null;
//儲存標準輸出流
InputStream stdIn = System.in;
//儲存標準輸入流
PrintStream stdOut = System.out;
//為原始碼匯入預設的包
code = "import java.util.*;\n" + code;
try {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
// define the diagnostic object, which will be used to save the
// diagnostic information
DiagnosticCollector<JavaFileObject> oDiagnosticCollector = new DiagnosticCollector<JavaFileObject>();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(oDiagnosticCollector, null, null);
// set class output location
fileManager.setLocation(StandardLocation.CLASS_OUTPUT, Arrays.asList(new File[] { new File(getClassOutput()) }));
StringSourceJavaObject sourceObject = new CompileAndRunJavaFile.StringSourceJavaObject(className, code);
Iterable<? extends JavaFileObject> fileObjects = Arrays.asList(sourceObject);
CompilationTask task = compiler.getTask(null, fileManager, oDiagnosticCollector, null, null, fileObjects);
boolean result = task.call();
if (result) {
Runtime runtime = Runtime.getRuntime();
Class<?> clazz = Class.forName(className);
Method method = clazz.getMethod(methodName, new Class<?>[]{String[].class});
//重置輸入流, 需要存放資料檔案的檔名
fis = new FileInputStream(new File("資料檔案地址"));
System.setIn(fis);
//重置輸出流,需要獲得控制檯的輸出
ByteArrayOutputStream bao = new ByteArrayOutputStream();
ps = new PrintStream(bao);
System.setOut(ps);
long startFreeMemory = runtime.freeMemory();//Java 虛擬機器中的空閒記憶體量
//執行時間也是無法知道,因為dos執行java命令,程式無法知道它到底執行到那裡了,兩個程序,互不瞭解
long startCurrentTime = System.currentTimeMillis();//獲取系統當前時間
method.invoke(null, new Object[]{null});
long endCurrentTime = System.currentTimeMillis();
long endFreeMemory = runtime.freeMemory();
//記憶體的使用情況,不是很精確
useMemory = (startFreeMemory-endFreeMemory)/1024.0;
if(useMemory > limitMemory) throw new Exception("Out Limit Memory!");
useTime = endCurrentTime-startCurrentTime;
if(useTime > limitTime) throw new Exception("Time Limited!");
//獲得控制檯的輸出
br = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bao.toByteArray())));
String outc = null;
while((outc = br.readLine()) != null)
outMsg.append(outc).append("\n");
//正常編譯並執行
isCompileAndRunOK = true;
} else {
isCompilerError = true;
//列印編譯的錯誤資訊
Pattern p = Pattern.compile("Main.java\\D*(\\d+):", Pattern.DOTALL);
for (Diagnostic<? extends JavaFileObject> oDiagnostic : oDiagnosticCollector.getDiagnostics()){
/*資訊示例:
Compiler Error: Main.java:8: 找不到符號
符號: 類 Scanner
位置: 類 Main
*/
//將行號減1
Matcher m = p.matcher("Compiler Error: " + oDiagnostic.getMessage(null));
if(m.find()) {
ce.append(m.replaceAll("Main.java " + String.valueOf(Integer.valueOf(m.group(1))-1)) + ":").append("\n");
} else {
ce.append("Compiler Error: " + oDiagnostic.getMessage(null)).append("\n");
}
}
}
} catch (Exception e) {
isRunningError = true;
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw, true));
Pattern p = Pattern.compile("Main.java\\D*(\\d+)", Pattern.DOTALL);
Matcher m = p.matcher(sw.toString());
if(m.find()){
error = m.replaceAll("Main.java " + String.valueOf(Integer.valueOf(m.group(1))-1) + ":");
} else {
error = sw.toString();
}
} finally {
//關閉流
try {
if(fis != null)
fis.close();
if(ps != null)
ps.close();
if(br != null)
br.close();
} catch (IOException e) {
e.printStackTrace();
}
//恢復輸入輸出流
System.setIn(stdIn);
System.setOut(stdOut);
}
}
private class StringSourceJavaObject extends SimpleJavaFileObject {
private String content = null;
public StringSourceJavaObject(String name, String content) {
super(URI.create(name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
this.content = content;
}
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return content;
}
}
}
執行結果:
1.正常執行
執行時間: 16ms
記憶體使用: 225.5546875kb
執行結果:
5 4 3 2 1
2.編譯錯誤
編譯錯誤: Compiler Error: Main.java 8 找不到符號
符號: 類 Scanner
位置: 類 Main:
Compiler Error: Main.java 8 找不到符號
符號: 類 Scanner
位置: 類 Main:
3.執行錯誤
(1)執行錯誤: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at com.test.CompileAndRunJavaFile.compileAndRunJavaFile(CompileAndRunJavaFile.java:163)
at com.test.CompileAndRunJavaFile.main(CompileAndRunJavaFile.java:44)
Caused by: java.lang.StackOverflowError
at Main.fun(Main.java 4:)
at Main.fun(Main.java 4:)
(2)執行錯誤: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at com.test.CompileAndRunJavaFile.compileAndRunJavaFile(CompileAndRunJavaFile.java:163)
at com.test.CompileAndRunJavaFile.main(CompileAndRunJavaFile.java:44)
Caused by: java.lang.ArrayIndexOutOfBoundsException: 6
at Main.main(Main.java 18:)
... 6 more
demo:
public class Main {
public static void fun(){
fun();
}
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
int[] array = new int[n];
for(int i=0; i<n; ++i)
array[i] = scan.nextInt();
for(int i=0; i<n; ++i)
System.out.print(array[i] + " ");
System.out.println();
//array[n+1] = 0;
//fun();
}
}