From cd18b3282c79f5ca04368644f9a82f37d10c7ab0 Mon Sep 17 00:00:00 2001 From: Claus Ibsen Date: Sun, 27 Mar 2022 14:52:57 +0200 Subject: [PATCH 1/4] #100: Prototype for compiling multiple classes in one go. --- .../main/java/org/joor/CompilationUnit.java | 56 +++ jOOR/src/main/java/org/joor/CompileUnit.java | 340 ++++++++++++++++++ jOOR/src/main/java/org/joor/Reflect.java | 5 + .../java/org/joor/test/CompileUnitTest.java | 75 ++++ 4 files changed, 476 insertions(+) create mode 100644 jOOR/src/main/java/org/joor/CompilationUnit.java create mode 100644 jOOR/src/main/java/org/joor/CompileUnit.java create mode 100644 jOOR/src/test/java/org/joor/test/CompileUnitTest.java diff --git a/jOOR/src/main/java/org/joor/CompilationUnit.java b/jOOR/src/main/java/org/joor/CompilationUnit.java new file mode 100644 index 0000000..1776d58 --- /dev/null +++ b/jOOR/src/main/java/org/joor/CompilationUnit.java @@ -0,0 +1,56 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.joor; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class CompilationUnit { + + private Map files = new LinkedHashMap<>(); + + public static class Result { + private final Map> classes = new LinkedHashMap<>(); + + public void addResult(String className, Class clazz) { + classes.put(className, clazz); + } + + public Class getClass(String className) { + return classes.get(className); + } + + public int size() { + return classes.size(); + } + + } + + public static CompilationUnit create() { + return new CompilationUnit(); + } + + public static CompilationUnit.Result result() { + return new Result(); + } + + public CompilationUnit unit(String className, String content) { + files.put(className, content); + return this; + } + + public Map getFiles() { + return files; + } +} diff --git a/jOOR/src/main/java/org/joor/CompileUnit.java b/jOOR/src/main/java/org/joor/CompileUnit.java new file mode 100644 index 0000000..746e0c6 --- /dev/null +++ b/jOOR/src/main/java/org/joor/CompileUnit.java @@ -0,0 +1,340 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.joor; + +/* [java-8] */ + +import javax.tools.FileObject; +import javax.tools.ForwardingJavaFileManager; +import javax.tools.JavaCompiler; +import javax.tools.JavaCompiler.CompilationTask; +import javax.tools.SimpleJavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.ToolProvider; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.OutputStream; +import java.io.StringWriter; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.net.URI; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Deque; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Stream; + +import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE; + +/** + * A utility that simplifies in-memory compilation of multiple new classes in the same unit. + * + * @author Lukas Eder + */ +public class CompileUnit { + + public static CompilationUnit.Result compileUnit(CompilationUnit unit, CompileOptions compileOptions) { + CompilationUnit.Result result = CompilationUnit.result(); + + // some classes may already be compiled so try to load them first + List files = new ArrayList<>(); + + Lookup lookup = MethodHandles.lookup(); + ClassLoader cl = lookup.lookupClass().getClassLoader(); + unit.getFiles().forEach((cn, code) -> { + try { + Class clazz = cl.loadClass(cn); + result.addResult(cn, clazz); + } + catch (ClassNotFoundException ignore) { + files.add(new CharSequenceJavaFileObject(cn, code)); + } + }); + + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + + try { + ClassFileManager fileManager = new ClassFileManager(compiler.getStandardFileManager(null, null, null)); + StringWriter out = new StringWriter(); + + List options = new ArrayList<>(compileOptions.options); + if (!options.contains("-classpath")) { + StringBuilder classpath = new StringBuilder(); + String separator = System.getProperty("path.separator"); + String cp = System.getProperty("java.class.path"); + String mp = System.getProperty("jdk.module.path"); + + if (cp != null && !"".equals(cp)) + classpath.append(cp); + if (mp != null && !"".equals(mp)) + classpath.append(mp); + + if (cl instanceof URLClassLoader) { + for (URL url : ((URLClassLoader) cl).getURLs()) { + if (classpath.length() > 0) + classpath.append(separator); + + if ("file".equals(url.getProtocol())) + classpath.append(new File(url.toURI())); + } + } + + options.addAll(Arrays.asList("-classpath", classpath.toString())); + } + + CompilationTask task = compiler.getTask(out, fileManager, null, options, null, files); + + if (!compileOptions.processors.isEmpty()) + task.setProcessors(compileOptions.processors); + + task.call(); + + if (fileManager.isEmpty()) + throw new ReflectException("Compilation error: " + out); + + // This works if we have private-access to the interfaces in the class hierarchy + if (Reflect.CACHED_LOOKUP_CONSTRUCTOR != null) { + for (CharSequenceJavaFileObject f : files) { + String className = f.getClassName(); + Class clazz = fileManager.loadAndReturnMainClass(className, + (name, bytes) -> Reflect.on(cl).call("defineClass", name, bytes, 0, bytes.length).get()); + if (clazz != null) { + result.addResult(className, clazz); + } + } + /* [java-9] */ + + // Lookup.defineClass() has only been introduced in Java 9. It is + // required to get private-access to interfaces in the class hierarchy + } else { + + // This method is called by client code from two levels up the current stack frame + // We need a private-access lookup from the class in that stack frame in order to get + // private-access to any local interfaces at that location. +// Class caller = StackWalker +// .getInstance(RETAIN_CLASS_REFERENCE) +// .walk(s -> s +// .skip(2) +// .findFirst() +// .get() +// .getDeclaringClass()); + + int index = 2; + for (CharSequenceJavaFileObject f : files) { + String className = f.getClassName(); + + Class caller = getClassFromIndex(index); + index++; + + // If the compiled class is in the same package as the caller class, then + // we can use the private-access Lookup of the caller class + if (className.startsWith(caller.getPackageName() + ".") && + + // [#74] This heuristic is necessary to prevent classes in subpackages of the caller to be loaded + // this way, as subpackages cannot access private content in super packages. + // The heuristic will work only with classes that follow standard naming conventions. + // A better implementation is difficult at this point. + Character.isUpperCase(className.charAt(caller.getPackageName().length() + 1))) { + Lookup privateLookup = MethodHandles.privateLookupIn(caller, lookup); + Class clazz = fileManager.loadAndReturnMainClass(className, + (name, bytes) -> privateLookup.defineClass(bytes)); + if (clazz != null) { + result.addResult(className, clazz); + } + } + + // Otherwise, use an arbitrary class loader. This approach doesn't allow for + // loading private-access interfaces in the compiled class's type hierarchy + else { + Compile.ByteArrayClassLoader c = new Compile.ByteArrayClassLoader(fileManager.classes()); + Class clazz = fileManager.loadAndReturnMainClass(className, + (name, bytes) -> c.loadClass(name)); + if (clazz != null) { + result.addResult(className, clazz); + } + } + } + } + /* [/java-9] */ + + return result; + } catch (ReflectException e) { + throw e; + } catch (Exception e) { + throw new ReflectException("Error while compiling unit " + unit, e); + } + } + + private static Class getClassFromIndex(int index) { + return StackWalker + .getInstance(RETAIN_CLASS_REFERENCE) + .walk(s -> s + .skip(index) + .findFirst() + .get() + .getDeclaringClass()); + } + + /* [java-9] */ + static final class ByteArrayClassLoader extends ClassLoader { + private final Map classes; + + ByteArrayClassLoader(Map classes) { + super(ByteArrayClassLoader.class.getClassLoader()); + + this.classes = classes; + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + byte[] bytes = classes.get(name); + + if (bytes == null) + return super.findClass(name); + else + return defineClass(name, bytes, 0, bytes.length); + } + } + /* [/java-9] */ + + static final class JavaFileObject extends SimpleJavaFileObject { + final ByteArrayOutputStream os = new ByteArrayOutputStream(); + + JavaFileObject(String name, Kind kind) { + super(URI.create("string:///" + name.replace('.', '/') + kind.extension), kind); + } + + byte[] getBytes() { + return os.toByteArray(); + } + + @Override + public OutputStream openOutputStream() { + return os; + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) { + return new String(os.toByteArray(), StandardCharsets.UTF_8); + } + } + + static final class ClassFileManager extends ForwardingJavaFileManager { + private final Map fileObjectMap; + private Map classes; + + ClassFileManager(StandardJavaFileManager standardManager) { + super(standardManager); + + fileObjectMap = new LinkedHashMap<>(); + } + + @Override + public JavaFileObject getJavaFileForOutput( + Location location, + String className, + JavaFileObject.Kind kind, + FileObject sibling + ) { + JavaFileObject result = new JavaFileObject(className, kind); + fileObjectMap.put(className, result); + return result; + } + + boolean isEmpty() { + return fileObjectMap.isEmpty(); + } + + Map classes() { + if (classes == null) { + classes = new LinkedHashMap<>(); + + for (Entry entry : fileObjectMap.entrySet()) + classes.put(entry.getKey(), entry.getValue().getBytes()); + } + + return classes; + } + + Class loadAndReturnMainClass(String mainClassName, ThrowingBiFunction> definer) throws Exception { + Class result = null; + + // [#117] We don't know the subclass hierarchy of the top level + // classes in the compilation unit, and we can't find out + // without either: + // + // - class loading them (which fails due to NoClassDefFoundError) + // - using a library like ASM (which is a big and painful dependency) + // + // Simple workaround: try until it works, in O(n^2), where n + // can be reasonably expected to be small. + Deque> queue = new ArrayDeque<>(classes().entrySet()); + int n1 = queue.size(); + + // Try at most n times + for (int i1 = 0; i1 < n1 && !queue.isEmpty(); i1++) { + int n2 = queue.size(); + + for (int i2 = 0; i2 < n2; i2++) { + Entry entry = queue.pop(); + + try { + Class c = definer.apply(entry.getKey(), entry.getValue()); + + if (mainClassName.equals(entry.getKey())) + result = c; + } catch (ReflectException e) { + queue.offer(entry); + } + } + } + + return result; + } + } + + @FunctionalInterface + interface ThrowingBiFunction { + R apply(T t, U u) throws Exception; + } + + static final class CharSequenceJavaFileObject extends SimpleJavaFileObject { + final CharSequence content; + final String className; + + public CharSequenceJavaFileObject(String className, CharSequence content) { + super(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE); + this.className = className; + this.content = content; + } + + public String getClassName() { + return className; + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) { + return content; + } + } +} +/* [/java-8] */ diff --git a/jOOR/src/main/java/org/joor/Reflect.java b/jOOR/src/main/java/org/joor/Reflect.java index e885abf..8aa9713 100644 --- a/jOOR/src/main/java/org/joor/Reflect.java +++ b/jOOR/src/main/java/org/joor/Reflect.java @@ -25,6 +25,7 @@ import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Optional; @@ -50,6 +51,10 @@ */ public class Reflect { + public static CompilationUnit.Result compileUnit(CompilationUnit unit) throws ReflectException { + return CompileUnit.compileUnit(unit, new CompileOptions()); + } + // --------------------------------------------------------------------- // Static API used as entrance points to the fluent API // --------------------------------------------------------------------- diff --git a/jOOR/src/test/java/org/joor/test/CompileUnitTest.java b/jOOR/src/test/java/org/joor/test/CompileUnitTest.java new file mode 100644 index 0000000..6d68ea4 --- /dev/null +++ b/jOOR/src/test/java/org/joor/test/CompileUnitTest.java @@ -0,0 +1,75 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.joor.test; + +import org.joor.CompilationUnit; +import org.joor.Reflect; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class CompileUnitTest { + + @Test + public void testSingleUnit() throws Exception { + CompilationUnit unit = CompilationUnit.create() + .unit("org.joor.test.CompileMultiTest1", + "package org.joor.test;\n" + + "class CompileMultiTest1 implements java.util.function.Supplier {\n" + + " public String get() {\n" + + " return \"Bye World!\";\n" + + " }\n" + + "}\n" + ); + + CompilationUnit.Result result = Reflect.compileUnit(unit); + assertEquals(1, result.size()); + + Reflect ref = Reflect.onClass(result.getClass("org.joor.test.CompileMultiTest1")); + Object out = ref.create().call("get").get(); + assertEquals("Bye World!", out); + } + + @Test + public void testDualUnit() throws Exception { + CompilationUnit unit = CompilationUnit.create() + .unit("org.joor.test.CompileMultiTest2", + "package org.joor.test;\n" + + "class CompileMultiTest2 implements java.util.function.Supplier {\n" + + " public String get() {\n" + + " return \"Bye World!\";\n" + + " }\n" + + "}\n" + ) + .unit("org.joor.test.CompileMultiTest3", + "package org.joor.test;\n" + + "class CompileMultiTest3 implements java.util.function.Supplier {\n" + + " public String get() {\n" + + " return \"Hi World!\";\n" + + " }\n" + + "}\n" + ); + + CompilationUnit.Result result = Reflect.compileUnit(unit); + assertEquals(2, result.size()); + + Reflect ref2 = Reflect.onClass(result.getClass("org.joor.test.CompileMultiTest2")); + Reflect ref3 = Reflect.onClass(result.getClass("org.joor.test.CompileMultiTest3")); + Object out2 = ref2.create().call("get").get(); + Object out3 = ref3.create().call("get").get(); + assertEquals("Bye World!", out2); + assertEquals("Hi World!", out3); + } + +} From 716162da8bd13e130c8da29ebabd93550a3f19fc Mon Sep 17 00:00:00 2001 From: Claus Ibsen Date: Sun, 27 Mar 2022 14:54:59 +0200 Subject: [PATCH 2/4] #100: Prototype for compiling multiple classes in one go. --- jOOR/src/main/java/org/joor/CompilationUnit.java | 13 +++++++++---- .../test/java/org/joor/test/CompileUnitTest.java | 10 +++++----- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/jOOR/src/main/java/org/joor/CompilationUnit.java b/jOOR/src/main/java/org/joor/CompilationUnit.java index 1776d58..ab5b7fd 100644 --- a/jOOR/src/main/java/org/joor/CompilationUnit.java +++ b/jOOR/src/main/java/org/joor/CompilationUnit.java @@ -15,10 +15,11 @@ import java.util.LinkedHashMap; import java.util.Map; +import java.util.Set; public class CompilationUnit { - private Map files = new LinkedHashMap<>(); + private final Map files = new LinkedHashMap<>(); public static class Result { private final Map> classes = new LinkedHashMap<>(); @@ -35,9 +36,13 @@ public int size() { return classes.size(); } + public Set getClassNames() { + return classes.keySet(); + } + } - public static CompilationUnit create() { + public static CompilationUnit input() { return new CompilationUnit(); } @@ -45,12 +50,12 @@ public static CompilationUnit.Result result() { return new Result(); } - public CompilationUnit unit(String className, String content) { + public CompilationUnit addClass(String className, String content) { files.put(className, content); return this; } - public Map getFiles() { + Map getFiles() { return files; } } diff --git a/jOOR/src/test/java/org/joor/test/CompileUnitTest.java b/jOOR/src/test/java/org/joor/test/CompileUnitTest.java index 6d68ea4..8d95c57 100644 --- a/jOOR/src/test/java/org/joor/test/CompileUnitTest.java +++ b/jOOR/src/test/java/org/joor/test/CompileUnitTest.java @@ -23,8 +23,8 @@ public class CompileUnitTest { @Test public void testSingleUnit() throws Exception { - CompilationUnit unit = CompilationUnit.create() - .unit("org.joor.test.CompileMultiTest1", + CompilationUnit unit = CompilationUnit.input() + .addClass("org.joor.test.CompileMultiTest1", "package org.joor.test;\n" + "class CompileMultiTest1 implements java.util.function.Supplier {\n" + " public String get() {\n" + @@ -43,8 +43,8 @@ public void testSingleUnit() throws Exception { @Test public void testDualUnit() throws Exception { - CompilationUnit unit = CompilationUnit.create() - .unit("org.joor.test.CompileMultiTest2", + CompilationUnit unit = CompilationUnit.input() + .addClass("org.joor.test.CompileMultiTest2", "package org.joor.test;\n" + "class CompileMultiTest2 implements java.util.function.Supplier {\n" + " public String get() {\n" + @@ -52,7 +52,7 @@ public void testDualUnit() throws Exception { " }\n" + "}\n" ) - .unit("org.joor.test.CompileMultiTest3", + .addClass("org.joor.test.CompileMultiTest3", "package org.joor.test;\n" + "class CompileMultiTest3 implements java.util.function.Supplier {\n" + " public String get() {\n" + From 540c9ad95c0901b6d173809cac303c7c08208df4 Mon Sep 17 00:00:00 2001 From: Claus Ibsen Date: Sun, 27 Mar 2022 15:07:41 +0200 Subject: [PATCH 3/4] #100: Prototype for compiling multiple classes in one go. --- .../{CompileUnit.java => MultiCompile.java} | 32 ++++++++--------- jOOR/src/main/java/org/joor/Reflect.java | 3 +- ...ileUnitTest.java => MultiCompileTest.java} | 36 ++++++++++++++++++- 3 files changed, 50 insertions(+), 21 deletions(-) rename jOOR/src/main/java/org/joor/{CompileUnit.java => MultiCompile.java} (94%) rename jOOR/src/test/java/org/joor/test/{CompileUnitTest.java => MultiCompileTest.java} (68%) diff --git a/jOOR/src/main/java/org/joor/CompileUnit.java b/jOOR/src/main/java/org/joor/MultiCompile.java similarity index 94% rename from jOOR/src/main/java/org/joor/CompileUnit.java rename to jOOR/src/main/java/org/joor/MultiCompile.java index 746e0c6..2b09195 100644 --- a/jOOR/src/main/java/org/joor/CompileUnit.java +++ b/jOOR/src/main/java/org/joor/MultiCompile.java @@ -35,13 +35,11 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.Deque; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.stream.Stream; import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE; @@ -50,8 +48,15 @@ * * @author Lukas Eder */ -public class CompileUnit { - +public class MultiCompile { + + /** + * Compiles multiple files as one unit + * + * @param unit the files to compile in the same unit + * @param compileOptions compile options + * @return the compilation result + */ public static CompilationUnit.Result compileUnit(CompilationUnit unit, CompileOptions compileOptions) { CompilationUnit.Result result = CompilationUnit.result(); @@ -130,24 +135,15 @@ public static CompilationUnit.Result compileUnit(CompilationUnit unit, CompileOp // This method is called by client code from two levels up the current stack frame // We need a private-access lookup from the class in that stack frame in order to get // private-access to any local interfaces at that location. -// Class caller = StackWalker -// .getInstance(RETAIN_CLASS_REFERENCE) -// .walk(s -> s -// .skip(2) -// .findFirst() -// .get() -// .getDeclaringClass()); - int index = 2; for (CharSequenceJavaFileObject f : files) { String className = f.getClassName(); - Class caller = getClassFromIndex(index); - index++; + Class caller = getClassFromIndex(index++); // If the compiled class is in the same package as the caller class, then // we can use the private-access Lookup of the caller class - if (className.startsWith(caller.getPackageName() + ".") && + if (caller != null && className.startsWith(caller.getPackageName() + ".") && // [#74] This heuristic is necessary to prevent classes in subpackages of the caller to be loaded // this way, as subpackages cannot access private content in super packages. @@ -185,13 +181,13 @@ public static CompilationUnit.Result compileUnit(CompilationUnit unit, CompileOp } private static Class getClassFromIndex(int index) { - return StackWalker + StackWalker.StackFrame sf = StackWalker .getInstance(RETAIN_CLASS_REFERENCE) .walk(s -> s .skip(index) .findFirst() - .get() - .getDeclaringClass()); + .orElse(null)); + return sf != null ? sf.getDeclaringClass() : null; } /* [java-9] */ diff --git a/jOOR/src/main/java/org/joor/Reflect.java b/jOOR/src/main/java/org/joor/Reflect.java index 8aa9713..1da139a 100644 --- a/jOOR/src/main/java/org/joor/Reflect.java +++ b/jOOR/src/main/java/org/joor/Reflect.java @@ -25,7 +25,6 @@ import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import java.util.Optional; @@ -52,7 +51,7 @@ public class Reflect { public static CompilationUnit.Result compileUnit(CompilationUnit unit) throws ReflectException { - return CompileUnit.compileUnit(unit, new CompileOptions()); + return MultiCompile.compileUnit(unit, new CompileOptions()); } // --------------------------------------------------------------------- diff --git a/jOOR/src/test/java/org/joor/test/CompileUnitTest.java b/jOOR/src/test/java/org/joor/test/MultiCompileTest.java similarity index 68% rename from jOOR/src/test/java/org/joor/test/CompileUnitTest.java rename to jOOR/src/test/java/org/joor/test/MultiCompileTest.java index 8d95c57..d0feacc 100644 --- a/jOOR/src/test/java/org/joor/test/CompileUnitTest.java +++ b/jOOR/src/test/java/org/joor/test/MultiCompileTest.java @@ -19,7 +19,7 @@ import static org.junit.Assert.assertEquals; -public class CompileUnitTest { +public class MultiCompileTest { @Test public void testSingleUnit() throws Exception { @@ -72,4 +72,38 @@ public void testDualUnit() throws Exception { assertEquals("Hi World!", out3); } + @Test + public void testClassLoadingOrder() throws Exception { + CompilationUnit unit = CompilationUnit.input() + .addClass("pm.A", "package pm; public class A extends B {}") + .addClass("pm.B", "package pm; public class B {}" + ); + + CompilationUnit.Result result = Reflect.compileUnit(unit); + assertEquals(2, result.size()); + + Class a = Reflect.onClass(result.getClass("pm.A")).type(); + Class b = Reflect.onClass(result.getClass("pm.B")).type(); + assertEquals("pm.A", a.getName()); + assertEquals("pm.B", a.getSuperclass().getName()); + assertEquals("pm.B", b.getName()); + } + + @Test + public void testClassLoadingOrderReverse() throws Exception { + CompilationUnit unit = CompilationUnit.input() + .addClass("pm.C", "package pm; public class C {}") + .addClass("pm.D", "package pm; public class D extends C {}" + ); + + CompilationUnit.Result result = Reflect.compileUnit(unit); + assertEquals(2, result.size()); + + Class c = Reflect.onClass(result.getClass("pm.C")).type(); + Class d = Reflect.onClass(result.getClass("pm.D")).type(); + assertEquals("pm.C", c.getName()); + assertEquals("pm.D", d.getName()); + assertEquals("pm.C", d.getSuperclass().getName()); + } + } From 9da98fc2d6fecc8a183e9e11ec8f387ae35ed9a9 Mon Sep 17 00:00:00 2001 From: Claus Ibsen Date: Sun, 27 Mar 2022 15:12:53 +0200 Subject: [PATCH 4/4] #100: Prototype for compiling multiple classes in one go. --- .../main/java/org/joor/CompilationUnit.java | 39 ++++++++++++++++--- jOOR/src/main/java/org/joor/MultiCompile.java | 6 +-- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/jOOR/src/main/java/org/joor/CompilationUnit.java b/jOOR/src/main/java/org/joor/CompilationUnit.java index ab5b7fd..de2c658 100644 --- a/jOOR/src/main/java/org/joor/CompilationUnit.java +++ b/jOOR/src/main/java/org/joor/CompilationUnit.java @@ -17,45 +17,72 @@ import java.util.Map; import java.util.Set; +/** + * Unit for holding multiple source files to be compiled in one go. + */ public class CompilationUnit { private final Map files = new LinkedHashMap<>(); + /** + * The result of the compilation that holds mapping for each className -> class. + */ public static class Result { private final Map> classes = new LinkedHashMap<>(); - public void addResult(String className, Class clazz) { + void addResult(String className, Class clazz) { classes.put(className, clazz); } + /** + * Gets the compiled class by its class name + * + * @param className the class name + * @return the compiled class + */ public Class getClass(String className) { return classes.get(className); } + /** + * Number of classes in the result + */ public int size() { return classes.size(); } + /** + * Set of the classes by their names + */ public Set getClassNames() { return classes.keySet(); } } - public static CompilationUnit input() { - return new CompilationUnit(); + static CompilationUnit.Result result() { + return new Result(); } - public static CompilationUnit.Result result() { - return new Result(); + /** + * Creates a new compilation unit for holding input files. + */ + public static CompilationUnit input() { + return new CompilationUnit(); } + /** + * Adds input to the compilation unit. + * + * @param className the class name + * @param content the source code for the class + */ public CompilationUnit addClass(String className, String content) { files.put(className, content); return this; } - Map getFiles() { + Map getInput() { return files; } } diff --git a/jOOR/src/main/java/org/joor/MultiCompile.java b/jOOR/src/main/java/org/joor/MultiCompile.java index 2b09195..4b90040 100644 --- a/jOOR/src/main/java/org/joor/MultiCompile.java +++ b/jOOR/src/main/java/org/joor/MultiCompile.java @@ -65,7 +65,7 @@ public static CompilationUnit.Result compileUnit(CompilationUnit unit, CompileOp Lookup lookup = MethodHandles.lookup(); ClassLoader cl = lookup.lookupClass().getClassLoader(); - unit.getFiles().forEach((cn, code) -> { + unit.getInput().forEach((cn, code) -> { try { Class clazz = cl.loadClass(cn); result.addResult(cn, clazz); @@ -139,7 +139,7 @@ public static CompilationUnit.Result compileUnit(CompilationUnit unit, CompileOp for (CharSequenceJavaFileObject f : files) { String className = f.getClassName(); - Class caller = getClassFromIndex(index++); + Class caller = findCompiledClassViaIndex(index++); // If the compiled class is in the same package as the caller class, then // we can use the private-access Lookup of the caller class @@ -180,7 +180,7 @@ public static CompilationUnit.Result compileUnit(CompilationUnit unit, CompileOp } } - private static Class getClassFromIndex(int index) { + private static Class findCompiledClassViaIndex(int index) { StackWalker.StackFrame sf = StackWalker .getInstance(RETAIN_CLASS_REFERENCE) .walk(s -> s