Skip to content

Commit

Permalink
统一通过java compiler api编译运行时生成的java源代码,不再依赖javac
Browse files Browse the repository at this point in the history
  • Loading branch information
codefollower committed Oct 21, 2022
1 parent 633accd commit 714f475
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 186 deletions.
230 changes: 46 additions & 184 deletions lealone-db/src/main/java/org/lealone/db/util/SourceCompiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,14 @@
package org.lealone.db.util;

import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringReader;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
Expand All @@ -46,12 +38,7 @@
import org.lealone.common.exceptions.DbException;
import org.lealone.common.logging.Logger;
import org.lealone.common.logging.LoggerFactory;
import org.lealone.common.util.IOUtils;
import org.lealone.common.util.StringUtils;
import org.lealone.common.util.Task;
import org.lealone.common.util.Utils;
import org.lealone.db.api.ErrorCode;
import org.lealone.storage.fs.FileUtils;

/**
* This class allows to convert source code to a class. It uses one class loader per class.
Expand All @@ -61,29 +48,15 @@
*/
public class SourceCompiler {

private static final Class<?> JAVAC_SUN;

/**
* The class name to source code map.
*/
final HashMap<String, String> sources = new HashMap<>();
private final HashMap<String, String> sources = new HashMap<>();

/**
* The class name to byte code map.
*/
final HashMap<String, Class<?>> compiled = new HashMap<>();

private final String compileDir = Utils.getProperty("java.io.tmpdir", ".");

static {
Class<?> clazz;
try {
clazz = Class.forName("com.sun.tools.javac.Main");
} catch (Exception e) {
clazz = null;
}
JAVAC_SUN = clazz;
}
private final HashMap<String, Class<?>> compiled = new HashMap<>();

/**
* Set the source code for the specified class.
Expand All @@ -97,19 +70,37 @@ public void setSource(String className, String source) {
compiled.clear();
}

/**
* Get the first public static method of the given class.
*
* @param className the class name
* @return the method name
*/
public Method getMethod(String className) throws ClassNotFoundException {
Class<?> clazz = getClass(className);
Method[] methods = clazz.getDeclaredMethods();
for (Method m : methods) {
int modifiers = m.getModifiers();
if (Modifier.isPublic(modifiers)) {
if (Modifier.isStatic(modifiers)) {
return m;
}
}
}
return null;
}

/**
* Get the class object for the given name.
*
* @param packageAndClassName the class name
* @return the class
*/
public Class<?> getClass(String packageAndClassName) throws ClassNotFoundException {

private Class<?> getClass(String packageAndClassName) throws ClassNotFoundException {
Class<?> compiledClass = compiled.get(packageAndClassName);
if (compiledClass != null) {
return compiledClass;
}

ClassLoader classLoader = new ClassLoader(getClass().getClassLoader()) {
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
Expand All @@ -125,7 +116,7 @@ public Class<?> findClass(String name) throws ClassNotFoundException {
} else {
className = name;
}
byte[] data = javacCompile(packageName, className, source);
byte[] data = compile(this, packageName, className, source);
if (data == null) {
classInstance = findSystemClass(name);
} else {
Expand All @@ -139,155 +130,26 @@ public Class<?> findClass(String name) throws ClassNotFoundException {
return classLoader.loadClass(packageAndClassName);
}

/**
* Get the first public static method of the given class.
*
* @param className the class name
* @return the method name
*/
public Method getMethod(String className) throws ClassNotFoundException {
Class<?> clazz = getClass(className);
Method[] methods = clazz.getDeclaredMethods();
for (Method m : methods) {
int modifiers = m.getModifiers();
if (Modifier.isPublic(modifiers)) {
if (Modifier.isStatic(modifiers)) {
return m;
}
private static byte[] compile(ClassLoader classLoader, String packageName, String className,
String source) {
if (!source.startsWith("package ")) {
StringBuilder buff = new StringBuilder();
int endImport = source.indexOf("@CODE");
String importCode = "import java.util.*;\n" + "import java.math.*;\n"
+ "import java.sql.*;\n";
if (endImport >= 0) {
importCode = source.substring(0, endImport);
source = source.substring("@CODE".length() + endImport);
}
}
return null;
}

/**
* Compile the given class. This method tries to use the class
* "com.sun.tools.javac.Main" if available. If not, it tries to run "javac"
* in a separate process.
*
* @param packageName the package name
* @param className the class name
* @param source the source code
* @return the class file
*/
byte[] javacCompile(String packageName, String className, String source) {
File dir = new File(compileDir);
if (packageName != null) {
dir = new File(dir, packageName.replace('.', '/'));
FileUtils.createDirectories(dir.getAbsolutePath());
}
File javaFile = new File(dir, className + ".java");
File classFile = new File(dir, className + ".class");
try {
OutputStream f = FileUtils.newOutputStream(javaFile.getAbsolutePath(), false);
PrintWriter out = new PrintWriter(IOUtils.getBufferedWriter(f));
classFile.delete();
if (source.startsWith("package ")) {
out.println(source);
} else {
int endImport = source.indexOf("@CODE");
String importCode = "import java.util.*;\n" + "import java.math.*;\n"
+ "import java.sql.*;\n";
if (endImport >= 0) {
importCode = source.substring(0, endImport);
source = source.substring("@CODE".length() + endImport);
}
if (packageName != null) {
out.println("package " + packageName + ";");
}
out.println(importCode);
out.println("public class " + className + " {\n" + " public static " + source + "\n"
+ "}\n");
}
out.close();
if (JAVAC_SUN != null) {
javacSun(javaFile);
} else {
javacProcess(javaFile);
}
byte[] data = new byte[(int) classFile.length()];
DataInputStream in = new DataInputStream(new FileInputStream(classFile));
in.readFully(data);
in.close();
return data;
} catch (Exception e) {
throw DbException.convert(e);
} finally {
javaFile.delete();
classFile.delete();
}
}

private void javacProcess(File javaFile) {
exec("javac", "-cp", cp(), "-sourcepath", compileDir, "-d", compileDir, "-encoding", "UTF-8",
javaFile.getAbsolutePath());
}

@SuppressWarnings("resource")
private String cp() {
StringBuilder cp = new StringBuilder();
ClassLoader cl = this.getClass().getClassLoader();
if (cl instanceof URLClassLoader) {
URLClassLoader urlCl = (URLClassLoader) cl;
for (URL url : urlCl.getURLs()) {
File file = new File(url.getFile());
cp.append(file.getAbsolutePath()).append(File.pathSeparatorChar);
}
}
cp.append(".");
return cp.toString();
}

private int exec(String... args) {
ByteArrayOutputStream buff = new ByteArrayOutputStream();
try {
Process p = Runtime.getRuntime().exec(args);
copyInThread(p.getInputStream(), buff);
copyInThread(p.getErrorStream(), buff);
p.waitFor();
String err = new String(buff.toByteArray(), "UTF-8");
throwSyntaxError(err);
return p.exitValue();
} catch (Exception e) {
throw DbException.convert(e);
}
}

private static void copyInThread(final InputStream in, final OutputStream out) {
new Task() {
@Override
public void call() throws IOException {
IOUtils.copy(in, out);
if (packageName != null) {
buff.append("package " + packageName + ";\n");
}
}.execute();
}

private void javacSun(File javaFile) {
PrintStream old = System.err;
ByteArrayOutputStream buff = new ByteArrayOutputStream();
PrintStream temp = new PrintStream(buff);
try {
System.setErr(temp);
Method compile;
compile = JAVAC_SUN.getMethod("compile", String[].class);
Object javac = JAVAC_SUN.getDeclaredConstructor().newInstance();
compile.invoke(javac, (Object) new String[] { "-sourcepath", compileDir, "-cp", cp(), "-d",
compileDir, "-encoding", "UTF-8", javaFile.getAbsolutePath() });
String err = new String(buff.toByteArray(), "UTF-8");
throwSyntaxError(err);
} catch (Exception e) {
throw DbException.convert(e);
} finally {
System.setErr(old);
}
}

private void throwSyntaxError(String err) {
if (err.startsWith("Note:")) {
// unchecked or unsafe operations - just a warning
} else if (err.length() > 0) {
err = StringUtils.replaceAll(err, compileDir, "");
throw DbException.get(ErrorCode.SYNTAX_ERROR_1, err);
buff.append(importCode).append("\n");
buff.append(
"public class " + className + " {\n" + " public static " + source + "\n" + "}\n");
source = buff.toString();
}
return compile(classLoader, className, source);
}

public static byte[] compile(String className, String sourceCode) {
Expand All @@ -303,15 +165,15 @@ public static byte[] compile(ClassLoader classLoader, String className, String s
}
}

public static Class<?> compileAsClass(String className, String sourceCode) {
return compileAsClass(SourceCompiler.class.getClassLoader(), className, sourceCode);
}

public static <T> T compileAsInstance(String className, String sourceCode) {
Class<?> clz = compileAsClass(className, sourceCode);
return Utils.newInstance(clz);
}

public static Class<?> compileAsClass(String className, String sourceCode) {
return compileAsClass(SourceCompiler.class.getClassLoader(), className, sourceCode);
}

public static Class<?> compileAsClass(ClassLoader classLoader, String className, String sourceCode) {
// 不能直接传递参数classLoader给compile,要传自定义SCClassLoader,否则会有各种类找不到的问题
SCClassLoader cl = new SCClassLoader(classLoader);
Expand Down Expand Up @@ -430,7 +292,7 @@ private static URI makeURI(final String canonicalClassName) {
}
}

public static class SCJavaCompiler {
private static class SCJavaCompiler {

private final boolean debug;
private final JavaCompiler compiler;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import org.lealone.db.result.Result;
import org.lealone.test.db.DbObjectTestBase;

//Ubuntu环境下运行这个测试,如果出现错误找不到javac,需要在~/.profile文件中把$JAVA_HOME/bin加到$PATH中
public class FunctionAliasTest extends DbObjectTestBase {

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ public class SourceCompilerTest {
public static void main(String[] args) throws Exception {
String name = SourceCompilerTest.class.getName();
String str = "public class Test { " + name + " f; public void m() {" + name + ".test(); }}";
// SourceCompiler.compile("Test", str);

Class<?> clz = SourceCompiler.compileAsClass("Test", str);
Object obj = clz.getDeclaredConstructor().newInstance();
Expand Down

0 comments on commit 714f475

Please sign in to comment.