remove compiler

This commit is contained in:
Looly 2023-03-03 18:03:59 +08:00
parent 0f06e591bb
commit a99dd899d6
19 changed files with 1 additions and 813 deletions

View File

@ -51,7 +51,7 @@
-------------------------------------------------------------------------------
## 📚Introduction
**Hutool** is a small but comprehensive library of Java tools, encapsulation by static methods, reduce the cost of learning related APIs, increase productivity, and make Java as elegant as a functional programming language,let the Java be "sweet" too.
**Hutool** is a small but comprehensive library of Java tools, achieved by encapsulation through static methods, reduce the cost of learning related APIs, increase productivity, and make Java as elegant as a functional programming language,let the Java be "sweet" too.
**Hutool** tools and methods from each user's crafted, it covers all aspects of the underlying code of Java development, it is a powerful tool for large project development to solve small problems, but also the efficiency of small projects;

View File

@ -1,34 +0,0 @@
package cn.hutool.core.compiler;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.text.StrUtil;
/**
* 编译异常
*
* @author looly
* @since 5.5.2
*/
public class CompilerException extends RuntimeException {
private static final long serialVersionUID = 1L;
public CompilerException(final Throwable e) {
super(ExceptionUtil.getMessage(e), e);
}
public CompilerException(final String message) {
super(message);
}
public CompilerException(final String messageTemplate, final Object... params) {
super(StrUtil.format(messageTemplate, params));
}
public CompilerException(final String message, final Throwable throwable) {
super(message, throwable);
}
public CompilerException(final Throwable throwable, final String messageTemplate, final Object... params) {
super(StrUtil.format(messageTemplate, params), throwable);
}
}

View File

@ -1,80 +0,0 @@
package cn.hutool.core.compiler;
import javax.tools.DiagnosticListener;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
/**
* 源码编译工具类主要封装{@link JavaCompiler} 相关功能
*
* @author looly
* @since 5.5.2
*/
public class CompilerUtil {
/**
* java 编译器
*/
public static final JavaCompiler SYSTEM_COMPILER = ToolProvider.getSystemJavaCompiler();
/**
* 编译指定的源码文件
*
* @param sourceFiles 源码文件路径
* @return 0表示成功否则其他
*/
public static boolean compile(final String... sourceFiles) {
return 0 == SYSTEM_COMPILER.run(null, null, null, sourceFiles);
}
/**
* 获取{@link StandardJavaFileManager}
*
* @return {@link StandardJavaFileManager}
*/
public static StandardJavaFileManager getFileManager() {
return getFileManager(null);
}
/**
* 获取{@link StandardJavaFileManager}
*
* @param diagnosticListener 异常收集器
* @return {@link StandardJavaFileManager}
* @since 5.5.8
*/
public static StandardJavaFileManager getFileManager(final DiagnosticListener<? super JavaFileObject> diagnosticListener) {
return SYSTEM_COMPILER.getStandardFileManager(diagnosticListener, null, null);
}
/**
* 新建编译任务
*
* @param fileManager {@link JavaFileManager}用于管理已经编译好的文件
* @param diagnosticListener 诊断监听
* @param options 选项例如 -cpXXX等
* @param compilationUnits 编译单元即需要编译的对象
* @return {@link JavaCompiler.CompilationTask}
*/
public static JavaCompiler.CompilationTask getTask(
final JavaFileManager fileManager,
final DiagnosticListener<? super JavaFileObject> diagnosticListener,
final Iterable<String> options,
final Iterable<? extends JavaFileObject> compilationUnits) {
return SYSTEM_COMPILER.getTask(null, fileManager, diagnosticListener, options, null, compilationUnits);
}
/**
* 获取{@link JavaSourceCompiler}
*
* @param parent {@link ClassLoader}
* @return {@link JavaSourceCompiler}
* @see JavaSourceCompiler#of(ClassLoader)
*/
public static JavaSourceCompiler getCompiler(final ClassLoader parent) {
return JavaSourceCompiler.of(parent);
}
}

View File

@ -1,26 +0,0 @@
package cn.hutool.core.compiler;
import javax.tools.DiagnosticCollector;
import java.util.List;
import java.util.stream.Collectors;
/**
* 诊断工具类
*
* @author looly
* @since 5.5.2
*/
public class DiagnosticUtil {
/**
* 获取{@link DiagnosticCollector}收集到的诊断信息以文本返回
*
* @param collector {@link DiagnosticCollector}
* @return 诊断消息
*/
public static String getMessages(final DiagnosticCollector<?> collector) {
final List<?> diagnostics = collector.getDiagnostics();
return diagnostics.stream().map(String::valueOf)
.collect(Collectors.joining(System.lineSeparator()));
}
}

View File

@ -1,78 +0,0 @@
package cn.hutool.core.compiler;
import cn.hutool.core.io.resource.FileObjectResource;
import cn.hutool.core.classloader.ResourceClassLoader;
import cn.hutool.core.classloader.ClassLoaderUtil;
import cn.hutool.core.util.ObjUtil;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import java.util.HashMap;
import java.util.Map;
/**
* Java 字节码文件对象管理器
*
* <p>
* 正常我们使用javac命令编译源码时会将class文件写入到磁盘中但在运行时动态编译类不适合保存在磁盘中
* 我们采取此对象来管理运行时动态编译类生成的字节码
* </p>
*
* @author lzpeng
* @since 5.5.2
*/
class JavaClassFileManager extends ForwardingJavaFileManager<JavaFileManager> {
/**
* 存储java字节码文件对象映射
*/
private final Map<String, FileObjectResource> classFileObjectMap = new HashMap<>();
/**
* 加载动态编译生成类的父类加载器
*/
private final ClassLoader parent;
/**
* 构造
*
* @param parent 父类加载器
* @param fileManager 字节码文件管理器
*/
protected JavaClassFileManager(final ClassLoader parent, final JavaFileManager fileManager) {
super(fileManager);
this.parent = ObjUtil.defaultIfNull(parent, ClassLoaderUtil::getClassLoader);
}
/**
* 获得动态编译生成的类的类加载器
*
* @param location 源码位置
* @return 动态编译生成的类的类加载器
*/
@Override
public ClassLoader getClassLoader(final Location location) {
return new ResourceClassLoader<>(this.parent, this.classFileObjectMap);
}
/**
* 获得Java字节码文件对象
* 编译器编译源码时会将Java源码对象编译转为Java字节码对象
*
* @param location 源码位置
* @param className 类名
* @param kind 文件类型
* @param sibling Java源码对象
* @return Java字节码文件对象
*/
@Override
public JavaFileObject getJavaFileForOutput(final Location location, final String className, final Kind kind, final FileObject sibling) {
final JavaFileObject javaFileObject = new JavaClassFileObject(className);
this.classFileObjectMap.put(className, new FileObjectResource(javaFileObject));
return javaFileObject;
}
}

View File

@ -1,60 +0,0 @@
package cn.hutool.core.compiler;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.net.url.URLUtil;
import javax.tools.SimpleJavaFileObject;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Java 字节码文件对象用于在内存中暂存class字节码从而可以在ClassLoader中动态加载
*
* @author lzpeng
* @since 5.5.2
*/
class JavaClassFileObject extends SimpleJavaFileObject {
/**
* 字节码输出流
*/
private final ByteArrayOutputStream byteArrayOutputStream;
/**
* 构造
*
* @param className 编译后的class文件的类名
* @see JavaClassFileManager#getJavaFileForOutput(javax.tools.JavaFileManager.Location, java.lang.String, javax.tools.JavaFileObject.Kind, javax.tools.FileObject)
*/
protected JavaClassFileObject(final String className) {
super(URLUtil.getStringURI(className.replace(CharUtil.DOT, CharUtil.SLASH) + Kind.CLASS.extension), Kind.CLASS);
this.byteArrayOutputStream = new ByteArrayOutputStream();
}
/**
* 获得字节码输入流
* 编译器编辑源码后我们将通过此输出流获得编译后的字节码以便运行时加载类
*
* @return 字节码输入流
* @see JavaClassFileManager#getClassLoader(javax.tools.JavaFileManager.Location)
*/
@Override
public InputStream openInputStream() {
return new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
}
/**
* 获得字节码输出流
* 编译器编辑源码时会将编译结果输出到本输出流中
*
* @return 字节码输出流
*/
@Override
public OutputStream openOutputStream() {
return this.byteArrayOutputStream;
}
}

View File

@ -1,112 +0,0 @@
package cn.hutool.core.compiler;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.compress.ZipUtil;
import cn.hutool.core.io.resource.FileResource;
import cn.hutool.core.io.resource.Resource;
import javax.tools.JavaFileObject;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipFile;
/**
* {@link JavaFileObject} 相关工具类封装
*
* @author lzpeng, looly
* @since 5.5.2
*/
public class JavaFileObjectUtil {
/**
* 获取指定资源下的所有待编译的java源码文件并以{@link JavaFileObject}形式返回
* <ul>
* <li>如果资源为目录则遍历目录找到目录中的.java或者.jar等文件加载之</li>
* <li>如果资源为.jar或.zip等解压读取其中的.java文件加载之</li>
* <li>其他情况直接读取资源流并加载之</li>
* </ul>
*
* @param resource 资源可以为目录文件或流
* @return 所有待编译的 {@link JavaFileObject}
*/
public static List<JavaFileObject> getJavaFileObjects(final Resource resource) {
final List<JavaFileObject> result = new ArrayList<>();
if (resource instanceof FileResource) {
final File file = ((FileResource) resource).getFile();
result.addAll(JavaFileObjectUtil.getJavaFileObjects(file));
} else {
result.add(new JavaSourceFileObject(resource.getName(), resource.getStream()));
}
return result;
}
/**
* 获取指定文件下的所有待编译的java文件并以{@link JavaFileObject}形式返回
* <ul>
* <li>如果文件为目录则遍历目录找到目录中的.java或者.jar等文件加载之</li>
* <li>如果文件为.jar或.zip等解压读取其中的.java文件加载之</li>
* </ul>
*
* @param file 文件或目录文件支持.java.jar和.zip文件
* @return 所有待编译的 {@link JavaFileObject}
*/
public static List<JavaFileObject> getJavaFileObjects(final File file) {
final List<JavaFileObject> result = new ArrayList<>();
if (file.isDirectory()) {
FileUtil.walkFiles(file, (subFile) -> result.addAll(getJavaFileObjects(file)));
} else {
final String fileName = file.getName();
if (isJavaFile(fileName)) {
result.add(new JavaSourceFileObject(file.toURI()));
} else if (isJarOrZipFile(fileName)) {
result.addAll(getJavaFileObjectByZipOrJarFile(file));
}
}
return result;
}
/**
* 是否是jar zip 文件<br>
* 通过扩展名判定
*
* @param fileName 文件名
* @return 是否是jar zip 文件
*/
public static boolean isJarOrZipFile(final String fileName) {
return FileNameUtil.isType(fileName, "jar", "zip");
}
/**
* 是否是java文件<br>
* 通过扩展名判定
*
* @param fileName 文件名
* @return 是否是.java文件
*/
public static boolean isJavaFile(final String fileName) {
return FileNameUtil.isType(fileName, "java");
}
/**
* 通过zip包或jar包创建Java文件对象
*
* @param file 压缩文件
* @return Java文件对象
*/
private static List<JavaFileObject> getJavaFileObjectByZipOrJarFile(final File file) {
final List<JavaFileObject> collection = new ArrayList<>();
final ZipFile zipFile = ZipUtil.toZipFile(file, null);
ZipUtil.read(zipFile, (zipEntry) -> {
final String name = zipEntry.getName();
if (isJavaFile(name)) {
collection.add(new JavaSourceFileObject(name, ZipUtil.getStream(zipFile, zipEntry)));
}
});
return collection;
}
}

View File

@ -1,234 +0,0 @@
package cn.hutool.core.compiler;
import cn.hutool.core.classloader.ClassLoaderUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.resource.FileResource;
import cn.hutool.core.io.resource.Resource;
import cn.hutool.core.io.resource.StringResource;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.net.url.URLUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjUtil;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.StandardLocation;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* Java 源码编译器
* <p>通过此类可以动态编译java源码并加载到ClassLoader从而动态获取加载的类</p>
* <p>JavaSourceCompiler支持加载的源码类型包括</p>
* <ul>
* <li>源码文件</li>
* <li>源码文件源码字符串</li>
* </ul>
*
* <p>使用方法如下</p>
* <pre>
* ClassLoader classLoader = JavaSourceCompiler.create(null)
* .addSource(FileUtil.file("test-compile/b/B.java"))
* .addSource("c.C", FileUtil.readUtf8String("test-compile/c/C.java"))
* // 增加编译依赖的类库
* .addLibrary(libFile)
* .compile();
* Class&lt;?&gt; clazz = classLoader.loadClass("c.C");
* </pre>
*
* @author lzpeng
*/
public class JavaSourceCompiler {
/**
* 待编译的资源支持
*
* <ul>
* <li>源码字符串使用{@link StringResource}</li>
* <li>源码文件源码jar包或源码zip包亦或者文件夹使用{@link FileResource}</li>
* </ul>
* 可以是 .java文件 压缩文件 文件夹 递归搜索文件夹内的zip包和jar包
*/
private final List<Resource> sourceList = new ArrayList<>();
/**
* 编译时需要加入classpath中的文件 可以是 压缩文件 文件夹递归搜索文件夹内的zip包和jar包
*/
private final List<File> libraryFileList = new ArrayList<>();
/**
* 编译类时使用的父类加载器
*/
private final ClassLoader parentClassLoader;
/**
* 创建Java源码编译器
*
* @param parent 父类加载器
* @return Java源码编译器
*/
public static JavaSourceCompiler of(final ClassLoader parent) {
return new JavaSourceCompiler(parent);
}
/**
* 构造
*
* @param parent 父类加载器null则使用默认类加载器
*/
private JavaSourceCompiler(final ClassLoader parent) {
this.parentClassLoader = ObjUtil.defaultIfNull(parent, ClassLoaderUtil::getClassLoader);
}
/**
* 向编译器中加入待编译的资源<br>
* 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
*
* @param resources 待编译的资源支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
* @return Java源码编译器
*/
public JavaSourceCompiler addSource(final Resource... resources) {
if (ArrayUtil.isNotEmpty(resources)) {
this.sourceList.addAll(Arrays.asList(resources));
}
return this;
}
/**
* 向编译器中加入待编译的文件<br>
* 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
*
* @param files 待编译的文件 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
* @return Java源码编译器
*/
public JavaSourceCompiler addSource(final File... files) {
if (ArrayUtil.isNotEmpty(files)) {
for (final File file : files) {
this.sourceList.add(new FileResource(file));
}
}
return this;
}
/**
* 向编译器中加入待编译的源码Map
*
* @param sourceCodeMap 源码Map key: 类名 value 源码
* @return Java源码编译器
*/
public JavaSourceCompiler addSource(final Map<String, String> sourceCodeMap) {
if (MapUtil.isNotEmpty(sourceCodeMap)) {
sourceCodeMap.forEach(this::addSource);
}
return this;
}
/**
* 向编译器中加入待编译的源码
*
* @param className 类名
* @param sourceCode 源码
* @return Java文件编译器
*/
public JavaSourceCompiler addSource(final String className, final String sourceCode) {
if (className != null && sourceCode != null) {
this.sourceList.add(new StringResource(sourceCode, className));
}
return this;
}
/**
* 加入编译Java源码时所需要的jar包jar包中必须为字节码
*
* @param files 编译Java源码时所需要的jar包
* @return Java源码编译器
*/
public JavaSourceCompiler addLibrary(final File... files) {
if (ArrayUtil.isNotEmpty(files)) {
this.libraryFileList.addAll(Arrays.asList(files));
}
return this;
}
/**
* 编译所有文件并返回类加载器
*
* @return 类加载器
*/
public ClassLoader compile() {
// 获得classPath
final List<File> classPath = getClassPath();
final URL[] urLs = URLUtil.getURLs(classPath.toArray(new File[0]));
final URLClassLoader ucl = URLClassLoader.newInstance(urLs, this.parentClassLoader);
if (sourceList.isEmpty()) {
// 没有需要编译的源码文件返回加载zip或jar包的类加载器
return ucl;
}
// 创建编译器
final JavaClassFileManager javaFileManager = new JavaClassFileManager(ucl, CompilerUtil.getFileManager());
// classpath
final List<String> options = new ArrayList<>();
if (false == classPath.isEmpty()) {
final List<String> cp = CollUtil.map(classPath, File::getAbsolutePath, true);
options.add("-cp");
options.add(CollUtil.join(cp, FileUtil.isWindows() ? ";" : ":"));
}
// 编译文件
final DiagnosticCollector<? super JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
final List<JavaFileObject> javaFileObjectList = getJavaFileObject();
final CompilationTask task = CompilerUtil.getTask(javaFileManager, diagnosticCollector, options, javaFileObjectList);
try {
if (task.call()) {
// 加载编译后的类
return javaFileManager.getClassLoader(StandardLocation.CLASS_OUTPUT);
}
} finally {
IoUtil.close(javaFileManager);
}
//编译失败,收集错误信息
throw new CompilerException(DiagnosticUtil.getMessages(diagnosticCollector));
}
/**
* 获得编译源码时需要的classpath
*
* @return 编译源码时需要的classpath
*/
private List<File> getClassPath() {
final List<File> classPathFileList = new ArrayList<>();
for (final File file : libraryFileList) {
final List<File> jarOrZipFile = FileUtil.loopFiles(file, (subFile) -> JavaFileObjectUtil.isJarOrZipFile(subFile.getName()));
classPathFileList.addAll(jarOrZipFile);
if (file.isDirectory()) {
classPathFileList.add(file);
}
}
return classPathFileList;
}
/**
* 获得待编译的Java文件对象
*
* @return 待编译的Java文件对象
*/
private List<JavaFileObject> getJavaFileObject() {
final List<JavaFileObject> list = new ArrayList<>();
for (final Resource resource : this.sourceList) {
list.addAll(JavaFileObjectUtil.getJavaFileObjects(resource));
}
return list;
}
}

View File

@ -1,90 +0,0 @@
package cn.hutool.core.compiler;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.net.url.URLUtil;
import javax.tools.SimpleJavaFileObject;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.charset.Charset;
/**
* Java 源码文件对象支持<br>
* <ol>
* <li>源文件通过文件的uri传入</li>
* <li>代码内容通过流传入</li>
* </ol>
*
* @author lzpeng
* @since 5.5.2
*/
class JavaSourceFileObject extends SimpleJavaFileObject {
/**
* 输入流
*/
private InputStream inputStream;
/**
* 构造支持File等路径类型的源码
*
* @param uri 需要编译的文件uri
*/
protected JavaSourceFileObject(final URI uri) {
super(uri, Kind.SOURCE);
}
/**
* 构造支持String类型的源码
*
* @param className 需要编译的类名
* @param code 需要编译的类源码
*/
protected JavaSourceFileObject(final String className, final String code, final Charset charset) {
this(className, IoUtil.toStream(code, charset));
}
/**
* 构造支持流中读取源码例如zip或网络等
*
* @param name 需要编译的文件名
* @param inputStream 输入流
*/
protected JavaSourceFileObject(final String name, final InputStream inputStream) {
this(URLUtil.getStringURI(name.replace(CharUtil.DOT, CharUtil.SLASH) + Kind.SOURCE.extension));
this.inputStream = inputStream;
}
/**
* 获得类源码的输入流
*
* @return 类源码的输入流
* @throws IOException IO 异常
*/
@Override
public InputStream openInputStream() throws IOException {
if (inputStream == null) {
inputStream = toUri().toURL().openStream();
}
return new BufferedInputStream(inputStream);
}
/**
* 获得类源码
* 编译器编辑源码前会通过此方法获取类的源码
*
* @param ignoreEncodingErrors 是否忽略编码错误
* @return 需要编译的类的源码
* @throws IOException IO异常
*/
@Override
public CharSequence getCharContent(final boolean ignoreEncodingErrors) throws IOException {
try(final InputStream in = openInputStream()){
return IoUtil.readUtf8(in);
}
}
}

View File

@ -1,6 +0,0 @@
/**
* 运行时编译java源码,动态从字符串或外部文件加载类
*
* @author : Lzpeng
*/
package cn.hutool.core.compiler;

View File

@ -1,53 +0,0 @@
package cn.hutool.core.compiler;
import cn.hutool.core.compress.ZipUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Console;
import cn.hutool.core.reflect.ConstructorUtil;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import java.io.File;
import java.io.InputStream;
/**
* Java源码编译器测试
*
* @author lzpeng
*/
public class JavaSourceCompilerTest {
@Test
@Ignore
public void compilerATest(){
final boolean compile = CompilerUtil.compile(FileUtil.file("test-compile/a/A.java").getAbsolutePath());
Assert.assertTrue(compile);
}
/**
* 测试编译Java源码
*/
@Test
public void testCompile() throws ClassNotFoundException {
// 依赖A编译B和C
final File libFile = ZipUtil.zip(FileUtil.file("lib.jar"),
new String[]{"a/A.class", "a/A$1.class", "a/A$InnerClass.class"},
new InputStream[]{
FileUtil.getInputStream("test-compile/a/A.class"),
FileUtil.getInputStream("test-compile/a/A$1.class"),
FileUtil.getInputStream("test-compile/a/A$InnerClass.class")
});
Console.log(libFile.getAbsolutePath());
final ClassLoader classLoader = CompilerUtil.getCompiler(null)
.addSource(FileUtil.file("test-compile/b/B.java"))
.addSource("c.C", FileUtil.readUtf8String("test-compile/c/C.java"))
.addLibrary(libFile)
// .addLibrary(FileUtil.file("D:\\m2_repo\\cn\\hutool\\hutool-all\\5.5.7\\hutool-all-5.5.7.jar"))
.compile();
final Class<?> clazz = classLoader.loadClass("c.C");
final Object obj = ConstructorUtil.newInstance(clazz);
Assert.assertTrue(String.valueOf(obj).startsWith("c.C@"));
}
}

View File

@ -1,22 +0,0 @@
package a;
import cn.hutool.core.lang.Console;
import cn.hutool.core.lang.ConsoleTable;
import cn.hutool.core.lang.caller.CallerUtil;
public class A {
private class InnerClass {
}
public A() {
new InnerClass() {{
int i = 0;
Console.log("初始化 " + getClass() + " 的调用链为: ");
Class<?> caller = CallerUtil.getCaller(i);
while (caller != null) {
Console.log("{} {}", caller, caller.getClassLoader());
caller = CallerUtil.getCaller(++i);
}
}};
}
}

View File

@ -1,8 +0,0 @@
package b;
import a.A;
public class B {
public B() {
new A();
}
}

View File

@ -1,9 +0,0 @@
package c;
import b.B;
public class C {
public C() {
new B();
}
}