diff --git a/pom.xml b/pom.xml
index 8c34d98..8c779ae 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
1.7.5
- 4.12
+ 4.13.1
diff --git a/src/main/java/org/mdkt/compiler/CompilationException.java b/src/main/java/org/mdkt/compiler/CompilationException.java
index 00c7f7f..feb3bcc 100644
--- a/src/main/java/org/mdkt/compiler/CompilationException.java
+++ b/src/main/java/org/mdkt/compiler/CompilationException.java
@@ -1,5 +1,8 @@
package org.mdkt.compiler;
+/**
+ * 编译异常类
+ */
public class CompilationException extends RuntimeException {
private static final long serialVersionUID = 5272588827551900536L;
diff --git a/src/main/java/org/mdkt/compiler/CompilationResult.java b/src/main/java/org/mdkt/compiler/CompilationResult.java
new file mode 100644
index 0000000..e2d396f
--- /dev/null
+++ b/src/main/java/org/mdkt/compiler/CompilationResult.java
@@ -0,0 +1,43 @@
+package org.mdkt.compiler;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.tools.Diagnostic;
+import javax.tools.JavaFileObject;
+
+/**
+ * 编译结果收集类
+ */
+public interface CompilationResult {
+ /**
+ * Return the compiled classes
+ */
+ Map> classMap() throws ClassNotFoundException;
+
+ /**
+ * Determine if the compilation did succeed
+ */
+ boolean compilationSucceeded();
+
+ /**
+ * Determine if any warnings are present
+ */
+ boolean hasWarnings();
+
+ /**
+ * Determine if any errors are present
+ */
+ boolean hasErrors();
+
+ /**
+ * Return the diagnostics produced by the compiler
+ */
+ List> getDiagnostics();
+
+ /**
+ * Throw an exception if the compilation did not succeed
+ */
+ CompilationResult checkNoErrors();
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/mdkt/compiler/CompiledCode.java b/src/main/java/org/mdkt/compiler/CompiledCode.java
index e6715cc..94b095c 100644
--- a/src/main/java/org/mdkt/compiler/CompiledCode.java
+++ b/src/main/java/org/mdkt/compiler/CompiledCode.java
@@ -1,5 +1,6 @@
package org.mdkt.compiler;
+import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -10,11 +11,11 @@
* Created by trung on 5/3/15.
*/
public class CompiledCode extends SimpleJavaFileObject {
- private ByteArrayOutputStream baos = new ByteArrayOutputStream();
- private String className;
+ private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ private final String className;
public CompiledCode(String className) throws Exception {
- super(new URI(className), Kind.CLASS);
+ super(new URI(className), JavaFileObject.Kind.CLASS);
this.className = className;
}
diff --git a/src/main/java/org/mdkt/compiler/DynamicClassLoader.java b/src/main/java/org/mdkt/compiler/DynamicClassLoader.java
index 2128bd1..86f6848 100644
--- a/src/main/java/org/mdkt/compiler/DynamicClassLoader.java
+++ b/src/main/java/org/mdkt/compiler/DynamicClassLoader.java
@@ -11,6 +11,10 @@ public DynamicClassLoader(ClassLoader parent) {
super(parent);
}
+ public Map getCustomCompiledCode(){
+ return this.customCompiledCode;
+ }
+
public void addCode(CompiledCode cc) {
customCompiledCode.put(cc.getName(), cc);
}
diff --git a/src/main/java/org/mdkt/compiler/ExtendedStandardJavaFileManager.java b/src/main/java/org/mdkt/compiler/ExtendedStandardJavaFileManager.java
index 1dc8a41..68806b7 100644
--- a/src/main/java/org/mdkt/compiler/ExtendedStandardJavaFileManager.java
+++ b/src/main/java/org/mdkt/compiler/ExtendedStandardJavaFileManager.java
@@ -1,15 +1,14 @@
package org.mdkt.compiler;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.stream.Collectors;
/**
* Created by trung on 5/3/15. Edited by turpid-monkey on 9/25/15, completed
@@ -18,20 +17,20 @@
public class ExtendedStandardJavaFileManager extends
ForwardingJavaFileManager {
- private List compiledCode = new ArrayList();
- private DynamicClassLoader cl;
+ private final Map compiledCode;
+ private final DynamicClassLoader cl;
/**
* Creates a new instance of ForwardingJavaFileManager.
*
- * @param fileManager
- * delegate to this file manager
+ * @param fileManager delegate to this file manager
* @param cl
*/
protected ExtendedStandardJavaFileManager(JavaFileManager fileManager,
- DynamicClassLoader cl) {
+ DynamicClassLoader cl, CompiledCode[] compiledCode) {
super(fileManager);
this.cl = cl;
+ this.compiledCode = Arrays.stream(compiledCode).collect(Collectors.toMap(CompiledCode::getClassName, c -> c));
}
@Override
@@ -40,8 +39,11 @@ public JavaFileObject getJavaFileForOutput(
JavaFileObject.Kind kind, FileObject sibling) throws IOException {
try {
- CompiledCode innerClass = new CompiledCode(className);
- compiledCode.add(innerClass);
+ CompiledCode innerClass = compiledCode.get(className);
+ if (innerClass == null) {
+ innerClass = new CompiledCode(className);
+ compiledCode.put(className, innerClass);
+ }
cl.addCode(innerClass);
return innerClass;
} catch (Exception e) {
diff --git a/src/main/java/org/mdkt/compiler/InMemoryJavaCompiler.java b/src/main/java/org/mdkt/compiler/InMemoryJavaCompiler.java
index b853590..534b7f4 100644
--- a/src/main/java/org/mdkt/compiler/InMemoryJavaCompiler.java
+++ b/src/main/java/org/mdkt/compiler/InMemoryJavaCompiler.java
@@ -1,142 +1,215 @@
package org.mdkt.compiler;
-import java.util.*;
-import javax.tools.*;
+import javax.tools.Diagnostic;
+import javax.tools.DiagnosticCollector;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileObject;
+import javax.tools.ToolProvider;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
/**
* Compile Java sources in-memory
*/
public class InMemoryJavaCompiler {
- private JavaCompiler javac;
- private DynamicClassLoader classLoader;
- private Iterable options;
- boolean ignoreWarnings = false;
-
- private Map sourceCodes = new HashMap();
-
- public static InMemoryJavaCompiler newInstance() {
- return new InMemoryJavaCompiler();
- }
-
- private InMemoryJavaCompiler() {
- this.javac = ToolProvider.getSystemJavaCompiler();
- this.classLoader = new DynamicClassLoader(ClassLoader.getSystemClassLoader());
- }
-
- public InMemoryJavaCompiler useParentClassLoader(ClassLoader parent) {
- this.classLoader = new DynamicClassLoader(parent);
- return this;
- }
-
- /**
- * @return the class loader used internally by the compiler
- */
- public ClassLoader getClassloader() {
- return classLoader;
- }
-
- /**
- * Options used by the compiler, e.g. '-Xlint:unchecked'.
- *
- * @param options
- * @return
- */
- public InMemoryJavaCompiler useOptions(String... options) {
- this.options = Arrays.asList(options);
- return this;
- }
-
- /**
- * Ignore non-critical compiler output, like unchecked/unsafe operation
- * warnings.
- *
- * @return
- */
- public InMemoryJavaCompiler ignoreWarnings() {
- ignoreWarnings = true;
- return this;
- }
-
- /**
- * Compile all sources
- *
- * @return Map containing instances of all compiled classes
- * @throws Exception
- */
- public Map> compileAll() throws Exception {
- if (sourceCodes.size() == 0) {
- throw new CompilationException("No source code to compile");
- }
- Collection compilationUnits = sourceCodes.values();
- CompiledCode[] code;
-
- code = new CompiledCode[compilationUnits.size()];
- Iterator iter = compilationUnits.iterator();
- for (int i = 0; i < code.length; i++) {
- code[i] = new CompiledCode(iter.next().getClassName());
- }
- DiagnosticCollector collector = new DiagnosticCollector<>();
- ExtendedStandardJavaFileManager fileManager = new ExtendedStandardJavaFileManager(javac.getStandardFileManager(null, null, null), classLoader);
- JavaCompiler.CompilationTask task = javac.getTask(null, fileManager, collector, options, null, compilationUnits);
- boolean result = task.call();
- if (!result || collector.getDiagnostics().size() > 0) {
- StringBuffer exceptionMsg = new StringBuffer();
- exceptionMsg.append("Unable to compile the source");
- boolean hasWarnings = false;
- boolean hasErrors = false;
- for (Diagnostic extends JavaFileObject> d : collector.getDiagnostics()) {
- switch (d.getKind()) {
- case NOTE:
- case MANDATORY_WARNING:
- case WARNING:
- hasWarnings = true;
- break;
- case OTHER:
- case ERROR:
- default:
- hasErrors = true;
- break;
- }
- exceptionMsg.append("\n").append("[kind=").append(d.getKind());
- exceptionMsg.append(", ").append("line=").append(d.getLineNumber());
- exceptionMsg.append(", ").append("message=").append(d.getMessage(Locale.US)).append("]");
- }
- if (hasWarnings && !ignoreWarnings || hasErrors) {
- throw new CompilationException(exceptionMsg.toString());
- }
- }
-
- Map> classes = new HashMap>();
- for (String className : sourceCodes.keySet()) {
- classes.put(className, classLoader.loadClass(className));
- }
- return classes;
- }
-
- /**
- * Compile single source
- *
- * @param className
- * @param sourceCode
- * @return
- * @throws Exception
- */
- public Class> compile(String className, String sourceCode) throws Exception {
- return addSource(className, sourceCode).compileAll().get(className);
- }
-
- /**
- * Add source code to the compiler
- *
- * @param className
- * @param sourceCode
- * @return
- * @throws Exception
- * @see {@link #compileAll()}
- */
- public InMemoryJavaCompiler addSource(String className, String sourceCode) throws Exception {
- sourceCodes.put(className, new SourceCode(className, sourceCode));
- return this;
- }
+ private final JavaCompiler javac;
+ private DynamicClassLoader classLoader;
+ private Iterable options;
+ boolean ignoreWarnings = false;
+
+ private Map sourceCodes = new HashMap<>();
+
+ public static InMemoryJavaCompiler newInstance() {
+ return new InMemoryJavaCompiler();
+ }
+
+ private InMemoryJavaCompiler() {
+ this.javac = ToolProvider.getSystemJavaCompiler();
+ this.classLoader = new DynamicClassLoader(ClassLoader.getSystemClassLoader());
+ }
+
+ public InMemoryJavaCompiler useParentClassLoader(ClassLoader parent) {
+ this.classLoader = new DynamicClassLoader(parent);
+ return this;
+ }
+
+ /**
+ * @return the class loader used internally by the compiler
+ */
+ public ClassLoader getClassloader() {
+ return classLoader;
+ }
+
+ /**
+ * Options used by the compiler, e.g. '-Xlint:unchecked'.
+ *
+ * @param options
+ * @return
+ */
+ public InMemoryJavaCompiler useOptions(String... options) {
+ this.options = Arrays.asList(options);
+ return this;
+ }
+
+ /**
+ * Ignore non-critical compiler output, like unchecked/unsafe operation
+ * warnings.
+ *
+ * @return
+ */
+ public InMemoryJavaCompiler ignoreWarnings() {
+ ignoreWarnings = true;
+ return this;
+ }
+
+ /**
+ * Compile all sources
+ *
+ * @return Map containing instances of all compiled classes
+ * @throws Exception
+ */
+ public Map> compileAll() throws Exception {
+ return compile().checkNoErrors().classMap();
+ }
+
+ /**
+ * Compile all sources added until now and return {@link CompilationResult},
+ * providing access to the compiled classes and/or errors and warnings
+ */
+ public CompilationResult compile() throws Exception {
+ if (sourceCodes.size() == 0) {
+ throw new CompilationException("No source code to compile");
+ }
+ Collection compilationUnits = sourceCodes.values();
+ CompiledCode[] code = new CompiledCode[compilationUnits.size()];
+ Iterator iter = compilationUnits.iterator();
+ for (int i = 0; i < code.length; i++) {
+ code[i] = new CompiledCode(iter.next().getClassName());
+ }
+ DiagnosticCollector collector = new DiagnosticCollector<>();
+ ExtendedStandardJavaFileManager fileManager = new ExtendedStandardJavaFileManager(javac.getStandardFileManager(null, null, null), classLoader, code);
+ JavaCompiler.CompilationTask task = javac.getTask(null, fileManager, collector, options, null, compilationUnits);
+ task.call();
+ boolean hasWarnings;
+ boolean hasErrors;
+
+ {
+ boolean hasWarningsTmp = false;
+ boolean hasErrorsTmp = false;
+ for (Diagnostic extends JavaFileObject> d : collector.getDiagnostics()) {
+ switch (d.getKind()) {
+ case NOTE:
+ case MANDATORY_WARNING:
+ case WARNING:
+ hasWarningsTmp = true;
+ break;
+ case OTHER:
+ case ERROR:
+ default:
+ hasErrorsTmp = true;
+ break;
+ }
+ }
+ hasWarnings = hasWarningsTmp;
+ hasErrors = hasErrorsTmp;
+ }
+
+ return new CompilationResult() {
+
+ @Override
+ public Map> classMap() throws ClassNotFoundException {
+ Map> classes = new HashMap<>();
+ for (String className : sourceCodes.keySet()) {
+ // 由loadClass变更为findClass,优先获取新编译的同名类(如果使用loadClass则获取不到新编译的同名class)
+ // 虽然支持多次加载同名类(指不同的ClassLoader,实际上同一个ClassLoader中Java仍然不允许重复会报错)
+ // 但是还是不推荐这样做,因为这样违反了Java的基本原则,且容易出现混淆影响自己问题的处理
+// classes.put(className, classLoader.loadClass(className));
+ classes.put(className, classLoader.findClass(className));
+ }
+ return classes;
+ }
+
+ @Override
+ public boolean compilationSucceeded() {
+ if (hasWarnings && !ignoreWarnings) return false;
+ return !hasErrors;
+ }
+
+ @Override
+ public boolean hasWarnings() {
+ return hasWarnings;
+ }
+
+ @Override
+ public boolean hasErrors() {
+ return hasErrors;
+ }
+
+ @Override
+ public List> getDiagnostics() {
+ return collector.getDiagnostics();
+ }
+
+ @Override
+ public CompilationResult checkNoErrors() {
+ if (!compilationSucceeded()) {
+ StringBuilder exceptionMsg = new StringBuilder();
+ exceptionMsg.append("Unable to compile the source");
+ for (Diagnostic extends JavaFileObject> d : getDiagnostics()) {
+ exceptionMsg.append("\n").append("[kind=").append(d.getKind());
+ exceptionMsg.append(", ").append("line=").append(d.getLineNumber());
+ exceptionMsg.append(", ").append("message=").append(d.getMessage(Locale.US)).append("]");
+ }
+ throw new CompilationException(exceptionMsg.toString());
+ }
+ return this;
+ }
+
+ };
+ }
+
+
+ /**
+ * Compile single source
+ *
+ * @param className
+ * @param sourceCode
+ * @return
+ * @throws Exception
+ */
+ public Class> compile(String className, String sourceCode) throws Exception {
+ return addSource(className, sourceCode).compileAll().get(className);
+ }
+
+ /**
+ * Add source code to the compiler
+ *
+ * @param className
+ * @param sourceCode
+ * @return
+ * @throws Exception
+ * @see {@link #compileAll()}
+ */
+ public InMemoryJavaCompiler addSource(String className, String sourceCode) throws Exception {
+ sourceCodes.put(className, new SourceCode(className, sourceCode));
+ return this;
+ }
+
+ /**
+ * Add source code map to the compiler
+ *
+ * @param sourceCodeMap
+ * @return
+ * @throws Exception
+ */
+ public InMemoryJavaCompiler addSources(Map sourceCodeMap) throws Exception {
+ sourceCodes.putAll(sourceCodeMap);
+ return this;
+ }
}
diff --git a/src/main/java/org/mdkt/compiler/SourceCode.java b/src/main/java/org/mdkt/compiler/SourceCode.java
index 9c7a9e7..acc9d49 100644
--- a/src/main/java/org/mdkt/compiler/SourceCode.java
+++ b/src/main/java/org/mdkt/compiler/SourceCode.java
@@ -1,5 +1,6 @@
package org.mdkt.compiler;
+import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import java.io.IOException;
import java.net.URI;
@@ -9,11 +10,11 @@
*/
public class SourceCode extends SimpleJavaFileObject {
private String contents = null;
- private String className;
+ private final String className;
public SourceCode(String className, String contents) throws Exception {
super(URI.create("string:///" + className.replace('.', '/')
- + Kind.SOURCE.extension), Kind.SOURCE);
+ + JavaFileObject.Kind.SOURCE.extension), JavaFileObject.Kind.SOURCE);
this.contents = contents;
this.className = className;
}
@@ -22,6 +23,7 @@ public String getClassName() {
return className;
}
+ @Override
public CharSequence getCharContent(boolean ignoreEncodingErrors)
throws IOException {
return contents;
diff --git a/src/test/java/org/mdkt/compiler/HelloClass.java b/src/test/java/org/mdkt/compiler/HelloClass.java
new file mode 100644
index 0000000..7694cc7
--- /dev/null
+++ b/src/test/java/org/mdkt/compiler/HelloClass.java
@@ -0,0 +1,8 @@
+package org.mdkt.compiler;
+
+public class HelloClass {
+
+ public String hello() {
+ return "hello";
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/mdkt/compiler/InMemoryJavaCompilerTest.java b/src/test/java/org/mdkt/compiler/InMemoryJavaCompilerTest.java
index c3c16e8..2e1b02a 100644
--- a/src/test/java/org/mdkt/compiler/InMemoryJavaCompilerTest.java
+++ b/src/test/java/org/mdkt/compiler/InMemoryJavaCompilerTest.java
@@ -1,8 +1,5 @@
package org.mdkt.compiler;
-import java.util.List;
-import java.util.Map;
-
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
@@ -10,6 +7,9 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.List;
+import java.util.Map;
+
public class InMemoryJavaCompilerTest {
private static final Logger logger = LoggerFactory.getLogger(InMemoryJavaCompilerTest.class);
@@ -18,7 +18,7 @@ public class InMemoryJavaCompilerTest {
@Test
public void compile_WhenTypical() throws Exception {
- StringBuffer sourceCode = new StringBuffer();
+ StringBuilder sourceCode = new StringBuilder();
sourceCode.append("package org.mdkt;\n");
sourceCode.append("public class HelloClass {\n");
@@ -30,19 +30,50 @@ public void compile_WhenTypical() throws Exception {
Assert.assertEquals(1, helloClass.getDeclaredMethods().length);
}
+ @Test
+ public void compile_WhenTypicalUpdateClass() throws Exception {
+ StringBuilder sourceCode = new StringBuilder();
+
+ sourceCode.append("package org.mdkt.compiler;\n");
+ sourceCode.append("public class HelloClass {\n");
+ sourceCode.append(" public String hello() { return \"hello1\"; }");
+ sourceCode.append("}");
+
+ Class> oldClass = HelloClass.class;
+ Class> newClass = InMemoryJavaCompiler.newInstance().compile("org.mdkt.compiler.HelloClass", sourceCode.toString());
+
+ Assert.assertNotEquals(oldClass.hashCode() , newClass.hashCode());
+ Assert.assertNotEquals(oldClass.getDeclaredMethod("hello").invoke(oldClass.newInstance()) ,
+ newClass.getDeclaredMethod("hello").invoke(newClass.newInstance()));
+ }
+
@Test
public void compileAll_WhenTypical() throws Exception {
String cls1 = "public class A{ public B b() { return new B(); }}";
String cls2 = "public class B{ public String toString() { return \"B!\"; }}";
+ String cls3 = "import org.mdkt.compiler.ShanhyTest; public class C{ public String hello() { return new ShanhyTest().hello(); }}";
- Map> compiled = InMemoryJavaCompiler.newInstance().addSource("A", cls1).addSource("B", cls2).compileAll();
+ Map> compiled = InMemoryJavaCompiler.newInstance()
+ .addSource("A", cls1)
+// .addSource("A", cls1)
+// .addSource("A", cls1)
+ .addSource("B", cls2)
+ .addSource("C", cls3)
+ .compileAll();
Assert.assertNotNull(compiled.get("A"));
Assert.assertNotNull(compiled.get("B"));
+ Assert.assertNotNull(compiled.get("C"));
Class> aClass = compiled.get("A");
Object a = aClass.newInstance();
Assert.assertEquals("B!", aClass.getMethod("b").invoke(a).toString());
+
+ Class> cClass = compiled.get("C");
+ Object c = cClass.newInstance();
+ String helloInvokeResult = cClass.getMethod("hello").invoke(c).toString();
+ System.out.println("helloInvokeResult >>> " + helloInvokeResult);
+ Assert.assertEquals("Hello Shanhy", helloInvokeResult);
}
@Test
@@ -87,21 +118,21 @@ public void compile_WhenFailOnWarnings() throws Exception {
@Test
public void compile_WhenIgnoreWarnings() throws Exception {
- StringBuffer sourceCode = new StringBuffer();
+ StringBuilder sourceCode = new StringBuilder();
sourceCode.append("package org.mdkt;\n");
sourceCode.append("public class HelloClass {\n");
sourceCode.append(" public java.util.List hello() { return new java.util.ArrayList(); }");
sourceCode.append("}");
Class> helloClass = InMemoryJavaCompiler.newInstance().ignoreWarnings().compile("org.mdkt.HelloClass", sourceCode.toString());
- List> res = (List>) helloClass.getMethod("hello").invoke(helloClass.newInstance());
+ List> res = (List>) helloClass.getMethod("hello").invoke(helloClass.getDeclaredConstructor().newInstance());
Assert.assertEquals(0, res.size());
}
@Test
public void compile_WhenWarningsAndErrors() throws Exception {
thrown.expect(CompilationException.class);
- StringBuffer sourceCode = new StringBuffer();
+ StringBuilder sourceCode = new StringBuilder();
sourceCode.append("package org.mdkt;\n");
sourceCode.append("public class HelloClass extends xxx {\n");
@@ -114,4 +145,5 @@ public void compile_WhenWarningsAndErrors() throws Exception {
throw e;
}
}
+
}
diff --git a/src/test/java/org/mdkt/compiler/ShanhyTest.java b/src/test/java/org/mdkt/compiler/ShanhyTest.java
new file mode 100644
index 0000000..2b1acd9
--- /dev/null
+++ b/src/test/java/org/mdkt/compiler/ShanhyTest.java
@@ -0,0 +1,13 @@
+package org.mdkt.compiler;
+
+/**
+ * @author shanhy
+ * @date 2023-06-05 15:43
+ */
+public class ShanhyTest {
+
+ public String hello(){
+ return "Hello Shanhy";
+ }
+
+}