diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/AsgcCompiler.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/AsgcCompiler.java new file mode 100644 index 00000000..b42f15af --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/AsgcCompiler.java @@ -0,0 +1,124 @@ +/** + * Copyright (c) 2022 aoshiguchen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package fun.asgc.neutrino.core.aop.compiler; + +import com.google.common.collect.Lists; +import fun.asgc.neutrino.core.base.GlobalConfig; +import fun.asgc.neutrino.core.util.CollectionUtil; +import lombok.extern.slf4j.Slf4j; + +import javax.tools.*; +import java.io.File; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * + * @author: aoshiguchen + * @date: 2022/8/17 + */ +@Slf4j +@SuppressWarnings("all") +public class AsgcCompiler { + private static final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + private DiagnosticCollector collector; + private List classpathList; + private boolean isSaveSourceCodeFile; + private boolean isSaveClassFile; + private String generatorCodeSavePath; + private ByteCodeClassLoader classLoader; + + public AsgcCompiler() { + if (null == compiler) { + throw new RuntimeException("Can not get javax.tools.JavaCompiler, check whether \"tools.jar\" is in the environment variable CLASSPATH \nVisit https://jfinal.com/doc/4-8 for details \n"); + } + this.collector = new DiagnosticCollector<>(); + this.classpathList = Lists.newArrayList(); + this.isSaveClassFile = false; + this.generatorCodeSavePath = GlobalConfig.getGeneratorCodeSavePath(); + this.classLoader = new ByteCodeClassLoader(); + } + + public void addClasspath(String classpath) { + this.classpathList.add(classpath); + } + + public void setSaveClassFile(boolean saveClassFile) { + this.isSaveClassFile = saveClassFile; + } + + public void setSaveSourceCodeFile(boolean saveSourceCodeFile) { + isSaveSourceCodeFile = saveSourceCodeFile; + } + + public void setGeneratorCodeSavePath(String generatorCodeSavePath) { + this.generatorCodeSavePath = generatorCodeSavePath; + } + + private List getOptions() { + List options = Lists.newArrayList(); + options.add("-target"); + options.add("1.8"); + if (!CollectionUtil.isEmpty(classpathList)) { + options.add("-classpath"); + options.add(classpathList.stream().collect(Collectors.joining(File.pathSeparator))); + } + return options; + } + + /** + * 编译代码 + * @param className 类名 + * @param sourceCode 源代码 + */ + public Map compile(String className, String sourceCode) { + DynamicJavaFileManager javaFileManager = new DynamicJavaFileManager(compiler.getStandardFileManager(collector, null, null)); + CharSequenceJavaFileObject javaFileObject = new CharSequenceJavaFileObject(className, sourceCode); + Boolean result = compiler.getTask(null, javaFileManager, collector, getOptions(), null, Arrays.asList(javaFileObject)).call(); + if (!result) { + collector.getDiagnostics().forEach(item -> log.error(item.toString())); + } + + Map ret = new HashMap<>(); + for (Map.Entry e : javaFileManager.fileObjects.entrySet()) { + ret.put(e.getKey(), e.getValue().getByteCode()); + } + return ret; + } + + /** + * 编译并加载类 + * @param pkg + * @param className + * @param sourceCode + * @param + * @return + */ + public Class compileAndLoadClass(String pkg, String className, String sourceCode) throws ClassNotFoundException { + Map byteCodeMap = compile(className, sourceCode); + classLoader.addByteCode(byteCodeMap); + return (Class)classLoader.loadClass(pkg + "." + className); + } +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/ByteCodeClassLoader.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/ByteCodeClassLoader.java new file mode 100644 index 00000000..dd669a21 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/ByteCodeClassLoader.java @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2022 aoshiguchen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package fun.asgc.neutrino.core.aop.compiler; + +import fun.asgc.neutrino.core.util.CollectionUtil; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * + * @author: aoshiguchen + * @date: 2022/8/17 + */ +public class ByteCodeClassLoader extends ClassLoader { + private Map byteCodeMap = new ConcurrentHashMap<>(); + static { + registerAsParallelCapable(); + } + + public ByteCodeClassLoader() { + super(getParentClassLoader()); + } + + protected static ClassLoader getParentClassLoader() { + ClassLoader ret = Thread.currentThread().getContextClassLoader(); + return ret != null ? ret : ByteCodeClassLoader.class.getClassLoader(); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + byte[] bytes = byteCodeMap.get(name); + if (null != bytes) { + Class ret = defineClass(name, bytes, 0, bytes.length); + byteCodeMap.remove(name); + return ret; + } + return super.findClass(name); + } + + public void addByteCode(Map byteCodeMap) { + if (!CollectionUtil.isEmpty(byteCodeMap)) { + for (Map.Entry e : byteCodeMap.entrySet()) { + this.byteCodeMap.putIfAbsent(e.getKey(), e.getValue()); + } + } + } +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/CharSequenceJavaFileObject.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/CharSequenceJavaFileObject.java new file mode 100644 index 00000000..81ffda3b --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/CharSequenceJavaFileObject.java @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2022 aoshiguchen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package fun.asgc.neutrino.core.aop.compiler; + +import javax.tools.JavaFileObject; +import javax.tools.SimpleJavaFileObject; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; + +/** + * + * @author: aoshiguchen + * @date: 2022/8/17 + */ +class CharSequenceJavaFileObject extends SimpleJavaFileObject { + private final CharSequence sourceCode; + private ByteArrayOutputStream out; + + public CharSequenceJavaFileObject(URI uri, Kind kind) { + super(uri, kind); + this.sourceCode = null; + } + + public CharSequenceJavaFileObject(String name, String source) { + super(URI.create(name + JavaFileObject.Kind.SOURCE.extension), JavaFileObject.Kind.SOURCE); + this.sourceCode = source; + } + + public CharSequenceJavaFileObject(String name, JavaFileObject.Kind kind) { + super(URI.create(name + kind.extension), kind); + this.sourceCode = null; + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) { + if (this.sourceCode == null) { + throw new IllegalStateException("源代码不能为空!"); + } + return sourceCode; + } + + @Override + public OutputStream openOutputStream() throws IOException { + if (null == out) { + this.out = new ByteArrayOutputStream(); + } + return out; + } + + public byte[] getByteCode() { + if (null == out) { + return null; + } + return out.toByteArray(); + } +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/DynamicJavaFileManager.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/DynamicJavaFileManager.java new file mode 100644 index 00000000..e435df9a --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/DynamicJavaFileManager.java @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2022 aoshiguchen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package fun.asgc.neutrino.core.aop.compiler; + +import javax.tools.FileObject; +import javax.tools.ForwardingJavaFileManager; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * + * @author: aoshiguchen + * @date: 2022/8/17 + */ +public class DynamicJavaFileManager extends ForwardingJavaFileManager { + public Map fileObjects = new HashMap<>(); + + public DynamicJavaFileManager(JavaFileManager fileManager) { + super(fileManager); + } + + @Override + public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String qualifiedClassName, JavaFileObject.Kind kind, FileObject sibling) throws IOException { + CharSequenceJavaFileObject javaFileObject = new CharSequenceJavaFileObject(qualifiedClassName, kind); + this.fileObjects.put(qualifiedClassName, javaFileObject); + return javaFileObject; + } + + @Override + public JavaFileObject getJavaFileForInput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind) throws IOException { + JavaFileObject javaFileObject = fileObjects.get(className); + if (javaFileObject == null) { + javaFileObject = super.getJavaFileForInput(location, className, kind); + } + return javaFileObject; + } +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyClass.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyClass.java index 3209f1c9..4d9df772 100644 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyClass.java +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyClass.java @@ -61,4 +61,8 @@ public class ProxyClass { this.target = target; this.pkg = target.getPackage().getName(); } + + public ProxyClass() { + + } } diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyCompiler.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyCompiler.java index 5097cf45..176a54b4 100644 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyCompiler.java +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyCompiler.java @@ -28,7 +28,6 @@ import lombok.extern.slf4j.Slf4j; import javax.tools.*; import java.io.*; -import java.lang.reflect.Method; import java.net.URI; import java.net.URL; import java.net.URLClassLoader; @@ -89,7 +88,7 @@ public class ProxyCompiler { } int index = 0; - boolean isWindows = isWindows(); + boolean isWindows = SystemUtil.isWindows(); StringBuilder ret = new StringBuilder(); for (URL url : classLoader.getURLs()) { if (index++ > 0) { @@ -124,11 +123,6 @@ public class ProxyCompiler { return list.stream().collect(Collectors.joining(File.pathSeparator)); } - protected boolean isWindows() { - String osName = System.getProperty("os.name", "unknown"); - return osName.toLowerCase().indexOf("windows") != -1; - } - protected URLClassLoader getURLClassLoader() { ClassLoader ret = Thread.currentThread().getContextClassLoader(); if (ret == null) { diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/SystemUtil.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/SystemUtil.java index ed9dc018..b72a9379 100644 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/SystemUtil.java +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/SystemUtil.java @@ -107,4 +107,9 @@ public class SystemUtil { } return url; } + + public static boolean isWindows() { + String osName = System.getProperty("os.name", "unknown"); + return osName.toLowerCase().indexOf("windows") != -1; + } } diff --git a/neutrino-core/src/test/java/fun/asgc/neutrino/core/aop/compiler/AsgcCompilerTest.java b/neutrino-core/src/test/java/fun/asgc/neutrino/core/aop/compiler/AsgcCompilerTest.java new file mode 100644 index 00000000..a9aae4e1 --- /dev/null +++ b/neutrino-core/src/test/java/fun/asgc/neutrino/core/aop/compiler/AsgcCompilerTest.java @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2022 aoshiguchen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package fun.asgc.neutrino.core.aop.compiler; + +import fun.asgc.neutrino.core.aop.proxy.ProxyClass; +import fun.asgc.neutrino.core.aop.proxy.ProxyClassLoader; +import fun.asgc.neutrino.core.aop.proxy.ProxyCompiler; +import fun.asgc.neutrino.core.util.ReflectUtil; +import org.junit.Test; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * + * @author: aoshiguchen + * @date: 2022/8/17 + */ +public class AsgcCompilerTest { + + @Test + public void test1() throws IllegalAccessException, InstantiationException, InvocationTargetException { + AsgcCompiler compiler = new AsgcCompiler(); + String code = "package a.b;\n" + + "public class Hello {\n" + + "\tpublic void hello() {\n" + + "\t\tSystem.out.println(\"hello\");\n" + + "\t}\n" + + "}\n"; + + ProxyClass proxyClass = new ProxyClass(); + proxyClass.setPkg("a.b"); + proxyClass.setName("Hello"); + proxyClass.setSourceCode(code); + ProxyCompiler proxyCompiler = new ProxyCompiler(); + proxyCompiler.compile(proxyClass); + ProxyClassLoader proxyClassLoader = new ProxyClassLoader(); + Class clazz = proxyClassLoader.loadProxyClass(proxyClass); + Method method = ReflectUtil.getMethods(clazz).stream().filter(m -> m.getName().equals("hello")).findFirst().get(); + Object instance = clazz.newInstance(); + method.invoke(instance); + } + + @Test + public void test2() throws ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException { + AsgcCompiler compiler = new AsgcCompiler(); + String code = "package a.b;\n" + + "public class Hello {\n" + + "\tpublic void hello() {\n" + + "\t\tSystem.out.println(\"hello\");\n" + + "\t}\n" + + "}\n"; + Class clazz = compiler.compileAndLoadClass("a.b","Hello", code); + Method method = ReflectUtil.getMethods(clazz).stream().filter(m -> m.getName().equals("hello")).findFirst().get(); + Object instance = clazz.newInstance(); + method.invoke(instance); + } + +}