簡單java程序在線測評程序
一.前言
大家過年好!今年的第一篇博客啊!家里沒有網,到處蹭無線!日子過得真糾結!因為畢設的需求,簡單寫了一個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
六.測試程序
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(); } }
