From e60a42c2b2f7cf3073a525905581ce4937eb2330 Mon Sep 17 00:00:00 2001 From: aoshiguchen <1052045476@qq.com> Date: Thu, 25 Aug 2022 23:30:06 +0800 Subject: [PATCH] =?UTF-8?q?AsgcCOmpiler=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/aop/compiler/AsgcCompiler.java | 91 ++++++------ .../aop/compiler/CustomJavaFileObject.java | 122 ++++++++++++++++ ...assLoader.java => DynamicClassLoader.java} | 55 ++++---- .../aop/compiler/DynamicJavaFileManager.java | 119 +++++++++++++--- ...avaFileObject.java => MemoryByteCode.java} | 58 ++++---- .../aop/compiler/PackageInternalsFinder.java | 130 ++++++++++++++++++ .../core/aop/compiler/StringSource.java | 46 +++++++ .../core/aop/compiler/AsgcCompilerTest.java | 10 +- 8 files changed, 508 insertions(+), 123 deletions(-) create mode 100644 neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/CustomJavaFileObject.java rename neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/{ByteCodeClassLoader.java => DynamicClassLoader.java} (53%) rename neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/{CharSequenceJavaFileObject.java => MemoryByteCode.java} (58%) create mode 100644 neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/PackageInternalsFinder.java create mode 100644 neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/StringSource.java 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 index 4015ef4a..7ac63cef 100644 --- 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 @@ -22,16 +22,14 @@ package fun.asgc.neutrino.core.aop.compiler; import com.google.common.collect.Lists; +import com.sun.tools.javac.resources.compiler; 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.*; import java.util.stream.Collectors; /** @@ -42,23 +40,34 @@ import java.util.stream.Collectors; @Slf4j @SuppressWarnings("all") public class AsgcCompiler { - private static final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); - private DiagnosticCollector collector; - private List classpathList; + private final JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler(); + private final DiagnosticCollector collector; + private final StandardJavaFileManager standardJavaFileManager; + private final List options = new ArrayList<>(); + private final List classpathList = new ArrayList<>(); + private final Collection compilationUnits = new ArrayList(); private boolean isSaveSourceCodeFile; private boolean isSaveClassFile; private String generatorCodeSavePath; - private ByteCodeClassLoader classLoader; + private DynamicClassLoader dynamicClassLoader; 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(ClassLoader.getSystemClassLoader()); + } + + public AsgcCompiler(ClassLoader classLoader) { + if (null == javaCompiler) { + throw new RuntimeException("Can not load JavaCompiler from javax.tools.ToolProvider#getSystemJavaCompiler(),\n please confirm the application running in JDK not JRE."); } this.collector = new DiagnosticCollector<>(); - this.classpathList = Lists.newArrayList(); + this.standardJavaFileManager = javaCompiler.getStandardFileManager(collector, null, null); this.isSaveClassFile = false; this.generatorCodeSavePath = GlobalConfig.getGeneratorCodeSavePath(); - this.classLoader = new ByteCodeClassLoader(); + this.dynamicClassLoader = new DynamicClassLoader(classLoader); + + addOption("-Xlint:unchecked"); + addOption("-source", "1.8"); + addOption("-target", "1.8"); } public void addClasspath(String classpath) { @@ -78,16 +87,29 @@ public class AsgcCompiler { } private List getOptions() { - List options = Lists.newArrayList(); - options.add("-source"); - options.add("1.8"); - options.add("-target"); - options.add("1.8"); + List list = Lists.newArrayList(options); if (!CollectionUtil.isEmpty(classpathList)) { - options.add("-classpath"); - options.add(classpathList.stream().collect(Collectors.joining(File.pathSeparator))); + list.add("-classpath"); + list.add(classpathList.stream().collect(Collectors.joining(File.pathSeparator))); } - return options; + return list; + } + + private void addOption(String option) { + this.options.add(option); + } + + private void addOption(String key, String val) { + this.options.add(key); + this.options.add(val); + } + + private void addSource(String className, String source) { + addSource(new StringSource(className, source)); + } + + private void addSource(JavaFileObject javaFileObject) { + compilationUnits.add(javaFileObject); } /** @@ -95,32 +117,17 @@ public class AsgcCompiler { * @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(); + public Class compile(String className, String sourceCode) throws ClassNotFoundException { + JavaFileManager javaFileManager = new DynamicJavaFileManager(standardJavaFileManager, dynamicClassLoader); + Boolean result = javaCompiler.getTask(null, javaFileManager, collector, getOptions(), null, Lists.newArrayList(new StringSource(className, sourceCode))).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()); + Map> map = dynamicClassLoader.getClasses(); + if (CollectionUtil.isEmpty(map)) { + return null; } - 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); + return map.values().stream().filter(c -> c.getSimpleName().equals(className)).findFirst().orElseGet(null); } } diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/CustomJavaFileObject.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/CustomJavaFileObject.java new file mode 100644 index 00000000..b41bec06 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/CustomJavaFileObject.java @@ -0,0 +1,122 @@ +/** + * 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.lang.model.element.Modifier; +import javax.lang.model.element.NestingKind; +import javax.tools.JavaFileObject; +import java.io.*; +import java.net.URI; + +/** + * + * @author: wen.y + * @date: 2022/8/25 + */ +public class CustomJavaFileObject implements JavaFileObject { + private final String binaryName; + private final URI uri; + private final String name; + + public CustomJavaFileObject(String binaryName, URI uri) { + this.uri = uri; + this.binaryName = binaryName; + name = uri.getPath() == null ? uri.getSchemeSpecificPart() : uri.getPath(); // for FS based URI the path is not null, for JAR URI the scheme specific part is not null + } + + @Override + public URI toUri() { + return uri; + } + + @Override + public InputStream openInputStream() throws IOException { + return uri.toURL().openStream(); + } + + @Override + public OutputStream openOutputStream() { + throw new UnsupportedOperationException(); + } + + @Override + public String getName() { + return name; + } + + @Override + public Reader openReader(boolean ignoreEncodingErrors) { + throw new UnsupportedOperationException(); + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) { + throw new UnsupportedOperationException(); + } + + @Override + public Writer openWriter() throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public long getLastModified() { + return 0; + } + + @Override + public boolean delete() { + throw new UnsupportedOperationException(); + } + + @Override + public Kind getKind() { + return Kind.CLASS; + } + + @Override + public boolean isNameCompatible(String simpleName, Kind kind) { + String baseName = simpleName + kind.extension; + return kind.equals(getKind()) + && (baseName.equals(getName()) + || getName().endsWith("/" + baseName)); + } + + @Override + public NestingKind getNestingKind() { + throw new UnsupportedOperationException(); + } + + @Override + public Modifier getAccessLevel() { + throw new UnsupportedOperationException(); + } + + public String binaryName() { + return binaryName; + } + + @Override + public String toString() { + return this.getClass().getName() + "[" + this.toUri() + "]"; + } +} 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/DynamicClassLoader.java similarity index 53% rename from neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/ByteCodeClassLoader.java rename to neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/DynamicClassLoader.java index dd669a21..07db14b4 100644 --- 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/DynamicClassLoader.java @@ -21,47 +21,48 @@ */ package fun.asgc.neutrino.core.aop.compiler; -import fun.asgc.neutrino.core.util.CollectionUtil; - +import java.util.HashMap; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; /** * - * @author: aoshiguchen - * @date: 2022/8/17 + * @author: wen.y + * @date: 2022/8/25 */ -public class ByteCodeClassLoader extends ClassLoader { - private Map byteCodeMap = new ConcurrentHashMap<>(); - static { - registerAsParallelCapable(); +public class DynamicClassLoader extends ClassLoader { + private final Map byteCodes = new HashMap<>(); + + public DynamicClassLoader(ClassLoader classLoader) { + super(classLoader); } - public ByteCodeClassLoader() { - super(getParentClassLoader()); - } - - protected static ClassLoader getParentClassLoader() { - ClassLoader ret = Thread.currentThread().getContextClassLoader(); - return ret != null ? ret : ByteCodeClassLoader.class.getClassLoader(); + public void registerCompiledSource(MemoryByteCode byteCode) { + byteCodes.put(byteCode.getClassName(), byteCode); } @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; + MemoryByteCode byteCode = byteCodes.get(name); + if (byteCode == null) { + return super.findClass(name); } - return super.findClass(name); + + return super.defineClass(name, byteCode.getByteCode(), 0, byteCode.getByteCode().length); } - public void addByteCode(Map byteCodeMap) { - if (!CollectionUtil.isEmpty(byteCodeMap)) { - for (Map.Entry e : byteCodeMap.entrySet()) { - this.byteCodeMap.putIfAbsent(e.getKey(), e.getValue()); - } + public Map> getClasses() throws ClassNotFoundException { + Map> classes = new HashMap>(); + for (MemoryByteCode byteCode : byteCodes.values()) { + classes.put(byteCode.getClassName(), findClass(byteCode.getClassName())); } + return classes; + } + + public Map getByteCodes() { + Map result = new HashMap(byteCodes.size()); + for (Map.Entry entry : byteCodes.entrySet()) { + result.put(entry.getKey(), entry.getValue().getByteCode()); + } + return result; } } 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 index e435df9a..6d906cb7 100644 --- 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 @@ -21,13 +21,9 @@ */ package fun.asgc.neutrino.core.aop.compiler; -import javax.tools.FileObject; -import javax.tools.ForwardingJavaFileManager; -import javax.tools.JavaFileManager; -import javax.tools.JavaFileObject; +import javax.tools.*; import java.io.IOException; -import java.util.HashMap; -import java.util.Map; +import java.util.*; /** * @@ -35,25 +31,112 @@ import java.util.Map; * @date: 2022/8/17 */ public class DynamicJavaFileManager extends ForwardingJavaFileManager { - public Map fileObjects = new HashMap<>(); + private static final String[] superLocationNames = { StandardLocation.PLATFORM_CLASS_PATH.name(), + /** JPMS StandardLocation.SYSTEM_MODULES **/ + "SYSTEM_MODULES" }; + private final PackageInternalsFinder finder; + private final DynamicClassLoader classLoader; + private final List byteCodes = new ArrayList(); - public DynamicJavaFileManager(JavaFileManager fileManager) { + public DynamicJavaFileManager(JavaFileManager fileManager, DynamicClassLoader classLoader) { super(fileManager); + this.classLoader = classLoader; + this.finder = new PackageInternalsFinder(classLoader); } @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; - } + public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className, + JavaFileObject.Kind kind, FileObject sibling) throws IOException { + for (MemoryByteCode byteCode : byteCodes) { + if (byteCode.getClassName().equals(className)) { + return byteCode; + } + } + + MemoryByteCode innerClass = new MemoryByteCode(className); + byteCodes.add(innerClass); + classLoader.registerCompiledSource(innerClass); + return innerClass; + + } @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); + public ClassLoader getClassLoader(JavaFileManager.Location location) { + return classLoader; + } + + @Override + public String inferBinaryName(Location location, JavaFileObject file) { + if (file instanceof CustomJavaFileObject) { + return ((CustomJavaFileObject) file).binaryName(); + } else { + /** + * if it's not CustomJavaFileObject, then it's coming from standard file manager + * - let it handle the file + */ + return super.inferBinaryName(location, file); + } + } + + @Override + public Iterable list(Location location, String packageName, Set kinds, + boolean recurse) throws IOException { + if (location instanceof StandardLocation) { + String locationName = ((StandardLocation) location).name(); + for (String name : superLocationNames) { + if (name.equals(locationName)) { + return super.list(location, packageName, kinds, recurse); + } + } + } + + // merge JavaFileObjects from specified ClassLoader + if (location == StandardLocation.CLASS_PATH && kinds.contains(JavaFileObject.Kind.CLASS)) { + return new IterableJoin(super.list(location, packageName, kinds, recurse), + finder.find(packageName)); + } + + return super.list(location, packageName, kinds, recurse); + } + + static class IterableJoin implements Iterable { + private final Iterable first, next; + + public IterableJoin(Iterable first, Iterable next) { + this.first = first; + this.next = next; + } + + @Override + public Iterator iterator() { + return new IteratorJoin(first.iterator(), next.iterator()); + } + } + + static class IteratorJoin implements Iterator { + private final Iterator first, next; + + public IteratorJoin(Iterator first, Iterator next) { + this.first = first; + this.next = next; + } + + @Override + public boolean hasNext() { + return first.hasNext() || next.hasNext(); + } + + @Override + public T next() { + if (first.hasNext()) { + return first.next(); + } + return next.next(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException("remove"); } - return javaFileObject; } } 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/MemoryByteCode.java similarity index 58% rename from neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/CharSequenceJavaFileObject.java rename to neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/MemoryByteCode.java index 81ffda3b..07428ff1 100644 --- 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/MemoryByteCode.java @@ -21,57 +21,53 @@ */ 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; +import java.net.URISyntaxException; /** * * @author: aoshiguchen - * @date: 2022/8/17 + * @date: 2022/8/25 */ -class CharSequenceJavaFileObject extends SimpleJavaFileObject { - private final CharSequence sourceCode; - private ByteArrayOutputStream out; +public class MemoryByteCode extends SimpleJavaFileObject { + private static final char PKG_SEPARATOR = '.'; + private static final char DIR_SEPARATOR = '/'; + private static final String CLASS_FILE_SUFFIX = ".class"; - public CharSequenceJavaFileObject(URI uri, Kind kind) { - super(uri, kind); - this.sourceCode = null; + private ByteArrayOutputStream byteArrayOutputStream; + + public MemoryByteCode(String className) { + super(URI.create("byte:///" + className.replace(PKG_SEPARATOR, DIR_SEPARATOR) + + Kind.CLASS.extension), Kind.CLASS); } - 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; + public MemoryByteCode(String className, ByteArrayOutputStream byteArrayOutputStream) + throws URISyntaxException { + this(className); + this.byteArrayOutputStream = byteArrayOutputStream; } @Override public OutputStream openOutputStream() throws IOException { - if (null == out) { - this.out = new ByteArrayOutputStream(); + if (byteArrayOutputStream == null) { + byteArrayOutputStream = new ByteArrayOutputStream(); } - return out; + return byteArrayOutputStream; } public byte[] getByteCode() { - if (null == out) { - return null; - } - return out.toByteArray(); + return byteArrayOutputStream.toByteArray(); } + + public String getClassName() { + String className = getName(); + className = className.replace(DIR_SEPARATOR, PKG_SEPARATOR); + className = className.substring(1, className.indexOf(CLASS_FILE_SUFFIX)); + return className; + } + } diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/PackageInternalsFinder.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/PackageInternalsFinder.java new file mode 100644 index 00000000..50723908 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/PackageInternalsFinder.java @@ -0,0 +1,130 @@ +/** + * 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 java.io.File; +import java.io.IOException; +import java.net.JarURLConnection; +import java.net.URI; +import java.net.URL; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.List; +import java.util.jar.JarEntry; + +/** + * + * @author: aoshiguchen + * @date: 2022/8/25 + */ +public class PackageInternalsFinder { + private final ClassLoader classLoader; + private static final String CLASS_FILE_EXTENSION = ".class"; + + public PackageInternalsFinder(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + public List find(String packageName) throws IOException { + String javaPackageName = packageName.replaceAll("\\.", "/"); + + List result = new ArrayList<>(); + + Enumeration urlEnumeration = classLoader.getResources(javaPackageName); + while (urlEnumeration.hasMoreElements()) { // one URL for each jar on the classpath that has the given package + URL packageFolderURL = urlEnumeration.nextElement(); + result.addAll(listUnder(packageName, packageFolderURL)); + } + + return result; + } + + private Collection listUnder(String packageName, URL packageFolderURL) { + File directory = new File(decode(packageFolderURL.getFile())); + if (directory.isDirectory()) { // browse local .class files - useful for local execution + return processDir(packageName, directory); + } else { // browse a jar file + return processJar(packageFolderURL); + } // maybe there can be something else for more involved class loaders + } + + private List processJar(URL packageFolderURL) { + List result = new ArrayList(); + try { + String jarUri = packageFolderURL.toExternalForm().substring(0, packageFolderURL.toExternalForm().lastIndexOf("!/")); + + JarURLConnection jarConn = (JarURLConnection) packageFolderURL.openConnection(); + String rootEntryName = jarConn.getEntryName(); + int rootEnd = rootEntryName.length() + 1; + + Enumeration entryEnum = jarConn.getJarFile().entries(); + while (entryEnum.hasMoreElements()) { + JarEntry jarEntry = entryEnum.nextElement(); + String name = jarEntry.getName(); + if (name.startsWith(rootEntryName) && name.indexOf('/', rootEnd) == -1 && name.endsWith(CLASS_FILE_EXTENSION)) { + URI uri = URI.create(jarUri + "!/" + name); + String binaryName = name.replaceAll("/", "."); + binaryName = binaryName.replaceAll(CLASS_FILE_EXTENSION + "$", ""); + + result.add(new CustomJavaFileObject(binaryName, uri)); + } + } + } catch (Exception e) { + throw new RuntimeException("Wasn't able to open " + packageFolderURL + " as a jar file", e); + } + return result; + } + + private List processDir(String packageName, File directory) { + List result = new ArrayList(); + + File[] childFiles = directory.listFiles(); + if (childFiles != null) { + for (File childFile : childFiles) { + if (childFile.isFile()) { + // We only want the .class files. + if (childFile.getName().endsWith(CLASS_FILE_EXTENSION)) { + String binaryName = packageName + "." + childFile.getName(); + binaryName = binaryName.replaceAll(CLASS_FILE_EXTENSION + "$", ""); + + result.add(new CustomJavaFileObject(binaryName, childFile.toURI())); + } + } + } + } + + return result; + } + + private String decode(String filePath) { + try { + return URLDecoder.decode(filePath, "utf-8"); + } catch (Exception e) { + // ignore, return original string + } + + return filePath; + } +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/StringSource.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/StringSource.java new file mode 100644 index 00000000..3359e411 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/StringSource.java @@ -0,0 +1,46 @@ +/** + * 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.SimpleJavaFileObject; +import java.io.IOException; +import java.net.URI; + +/** + * + * @author: aoshiguchen + * @date: 2022/8/25 + */ +public class StringSource extends SimpleJavaFileObject { + private final String contents; + + public StringSource(String className, String contents) { + super(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE); + this.contents = contents; + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { + return contents; + } + +} 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 index ed5efb7c..b9b36d17 100644 --- 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 @@ -43,7 +43,7 @@ public class AsgcCompilerTest { "\t\tSystem.out.println(\"hello\");\n" + "\t}\n" + "}\n"; - Class clazz = compiler.compileAndLoadClass("a.b","Hello", code); + Class clazz = compiler.compile("Hello", code); Method method = ReflectUtil.getMethods(clazz).stream().filter(m -> m.getName().equals("hello")).findFirst().get(); Object instance = clazz.newInstance(); method.invoke(instance); @@ -59,7 +59,7 @@ public class AsgcCompilerTest { "\t\tSystem.out.println(\"熊猫正在吃\" + food);\n" + "\t}\n" + "}\n"; - Class clazz = compiler.compileAndLoadClass("a.b","Panda", code); + Class clazz = compiler.compile("Panda", code); Method method = ReflectUtil.getMethods(clazz).stream().filter(m -> m.getName().equals("eat")).findFirst().get(); Object instance = clazz.newInstance(); method.invoke(instance, "竹子"); @@ -76,7 +76,7 @@ public class AsgcCompilerTest { "\t\tSystem.out.println(\"收音机播放\");\n" + "\t}\n" + "}\n"; - Class clazz = compiler.compileAndLoadClass("a.b","RadioPlayer", code); + Class clazz = compiler.compile("RadioPlayer", code); Method method = ReflectUtil.getMethods(clazz).stream().filter(m -> m.getName().equals("play")).findFirst().get(); Object instance = clazz.newInstance(); method.invoke(instance); @@ -97,7 +97,7 @@ public class AsgcCompilerTest { "\t\tSystem.out.println(\"收音机播放\");\n" + "\t}\n" + "}\n"; - Class clazz = compiler.compileAndLoadClass("a.b","RadioPlayer", code); + Class clazz = compiler.compile("RadioPlayer", code); Method method = ReflectUtil.getMethods(clazz).stream().filter(m -> m.getName().equals("play")).findFirst().get(); Object instance = clazz.newInstance(); method.invoke(instance); @@ -113,7 +113,7 @@ public class AsgcCompilerTest { "}\n"; AsgcCompiler compiler = new AsgcCompiler(); try { - Class clazz = compiler.compileAndLoadClass("fun.asgc.test", "Calc", code); + Class clazz = compiler.compile( "Calc", code); Method method = ReflectUtil.getMethods(clazz).stream().filter(m -> m.getName().equals("invoke")).findFirst().get(); return method.invoke(null); } catch (ClassNotFoundException|IllegalAccessException|InvocationTargetException e) {