Skip to content

Commit

Permalink
[#71] Compiled inner class throws java.lang.ClassNotFoundException
Browse files Browse the repository at this point in the history
  • Loading branch information
lukaseder committed Dec 4, 2018
1 parent 33becf3 commit fb03168
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 29 deletions.
41 changes: 41 additions & 0 deletions jOOR-java-6/src/main/java/org/joor/Compile.java
Expand Up @@ -197,6 +197,47 @@















































Expand Down
18 changes: 18 additions & 0 deletions jOOR-java-6/src/test/java/org/joor/test/CompileTest.java
Expand Up @@ -145,6 +145,24 @@
























Expand Down
61 changes: 51 additions & 10 deletions jOOR-java-8/src/main/java/org/joor/Compile.java
Expand Up @@ -26,9 +26,12 @@
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.*;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Map.Entry;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
Expand Down Expand Up @@ -90,15 +93,15 @@ static Class<?> compile(String className, String content, CompileOptions compile

task.call();

if (fileManager.o == null)
if (fileManager.isEmpty())
throw new ReflectException("Compilation error: " + out);

Class<?> result = null;
Class<?> result;

// This works if we have private-access to the interfaces in the class hierarchy
if (Reflect.CACHED_LOOKUP_CONSTRUCTOR != null) {
byte[] b = fileManager.o.getBytes();
result = Reflect.on(cl).call("defineClass", className, b, 0, b.length).get();
result = fileManager.loadAndReturnMainClass(className,
(name, bytes) -> Reflect.on(cl).call("defineClass", name, bytes, 0, bytes.length).get());
}


Expand Down Expand Up @@ -136,6 +139,8 @@ static Class<?> compile(String className, String content, CompileOptions compile








Expand Down Expand Up @@ -166,7 +171,6 @@ static Class<?> compile(String className, String content, CompileOptions compile




static final class JavaFileObject extends SimpleJavaFileObject {
final ByteArrayOutputStream os = new ByteArrayOutputStream();

Expand All @@ -185,10 +189,13 @@ public OutputStream openOutputStream() {
}

static final class ClassFileManager extends ForwardingJavaFileManager<StandardJavaFileManager> {
JavaFileObject o;
private final Map<String, JavaFileObject> fileObjectMap;
private Map<String, byte[]> classes;

ClassFileManager(StandardJavaFileManager standardManager) {
super(standardManager);

fileObjectMap = new HashMap<>();
}

@Override
Expand All @@ -198,8 +205,42 @@ public JavaFileObject getJavaFileForOutput(
JavaFileObject.Kind kind,
FileObject sibling
) {
return o = new JavaFileObject(className, kind);
JavaFileObject result = new JavaFileObject(className, kind);
fileObjectMap.put(className, result);
return result;
}

boolean isEmpty() {
return fileObjectMap.isEmpty();
}

Map<String, byte[]> classes() {
if (classes == null) {
classes = new HashMap<>();

for (Entry<String, JavaFileObject> entry : fileObjectMap.entrySet())
classes.put(entry.getKey(), entry.getValue().getBytes());
}

return classes;
}

Class<?> loadAndReturnMainClass(String mainClassName, ThrowingBiFunction<String, byte[], Class<?>> definer) throws Exception {
Class<?> result = null;

for (Entry<String, byte[]> entry : classes().entrySet()) {
Class<?> c = definer.apply(entry.getKey(), entry.getValue());
if (mainClassName.equals(entry.getKey()))
result = c;
}

return result;
}
}

@FunctionalInterface
interface ThrowingBiFunction<T, U, R> {
R apply(T t, U u) throws Exception;
}

static final class CharSequenceJavaFileObject extends SimpleJavaFileObject {
Expand Down
18 changes: 18 additions & 0 deletions jOOR-java-8/src/test/java/org/joor/test/CompileTest.java
Expand Up @@ -155,6 +155,24 @@ public void testCompileNestedClass() {
int foo = Reflect.on(c).create().call("inner").call("foo").get();
assertEquals(42, foo);
}

@Test
public void testCompileTopLevelClasses() {
Class<?> c =
Reflect.compile(
"org.joor.test.CompileTopLevelClasses",
"package org.joor.test;" +
"public class CompileTopLevelClasses {" +
"Other other() { return new Other(); }" +
"}" +
"class Other {" +
"int foo() { return 42; }" +
"}")
.get();

int foo = Reflect.on(c).create().call("other").call("foo").get();
assertEquals(42, foo);
}
}

interface I extends J {
Expand Down
79 changes: 60 additions & 19 deletions jOOR/src/main/java/org/joor/Compile.java
Expand Up @@ -26,9 +26,12 @@
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.*;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Map.Entry;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
Expand Down Expand Up @@ -90,15 +93,15 @@ static Class<?> compile(String className, String content, CompileOptions compile

task.call();

if (fileManager.o == null)
if (fileManager.isEmpty())
throw new ReflectException("Compilation error: " + out);

Class<?> result = null;
Class<?> result;

// This works if we have private-access to the interfaces in the class hierarchy
if (Reflect.CACHED_LOOKUP_CONSTRUCTOR != null) {
byte[] b = fileManager.o.getBytes();
result = Reflect.on(cl).call("defineClass", className, b, 0, b.length).get();
result = fileManager.loadAndReturnMainClass(className,
(name, bytes) -> Reflect.on(cl).call("defineClass", name, bytes, 0, bytes.length).get());
}
/* [java-9] */

Expand Down Expand Up @@ -126,15 +129,17 @@ static Class<?> compile(String className, String content, CompileOptions compile
// 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))) {
result = MethodHandles
.privateLookupIn(caller, lookup)
.defineClass(fileManager.o.getBytes());
Lookup privateLookup = MethodHandles.privateLookupIn(caller, lookup);
result = fileManager.loadAndReturnMainClass(className,
(name, bytes) -> privateLookup.defineClass(bytes));
}

// Otherwise, use an arbitrary class loader. This approach doesn't allow for
// loading private-access interfaces in the compiled class's type hierarchy
else {
result = new ByteArrayClassLoader(className, fileManager.o.getBytes()).loadClass(className);
ByteArrayClassLoader c = new ByteArrayClassLoader(fileManager.classes());
result = fileManager.loadAndReturnMainClass(className,
(name, bytes) -> c.loadClass(name));
}
}
/* [/java-9] */
Expand All @@ -152,17 +157,16 @@ static Class<?> compile(String className, String content, CompileOptions compile

/* [java-9] */
static final class ByteArrayClassLoader extends ClassLoader {
private final String className;
private final byte[] bytes;
private final Map<String, byte[]> classes;

ByteArrayClassLoader(String className, byte[] bytes) {
this.className = className;
this.bytes = bytes;
ByteArrayClassLoader(Map<String, byte[]> classes) {
this.classes = classes;
}

@Override
protected Class<?> findClass(String name) {
return defineClass(className, bytes, 0, bytes.length);
byte[] bytes = classes.get(name);
return defineClass(name, bytes, 0, bytes.length);
}
}
/* [/java-9] */
Expand All @@ -185,10 +189,13 @@ public OutputStream openOutputStream() {
}

static final class ClassFileManager extends ForwardingJavaFileManager<StandardJavaFileManager> {
JavaFileObject o;
private final Map<String, JavaFileObject> fileObjectMap;
private Map<String, byte[]> classes;

ClassFileManager(StandardJavaFileManager standardManager) {
super(standardManager);

fileObjectMap = new HashMap<>();
}

@Override
Expand All @@ -198,8 +205,42 @@ public JavaFileObject getJavaFileForOutput(
JavaFileObject.Kind kind,
FileObject sibling
) {
return o = new JavaFileObject(className, kind);
JavaFileObject result = new JavaFileObject(className, kind);
fileObjectMap.put(className, result);
return result;
}

boolean isEmpty() {
return fileObjectMap.isEmpty();
}

Map<String, byte[]> classes() {
if (classes == null) {
classes = new HashMap<>();

for (Entry<String, JavaFileObject> entry : fileObjectMap.entrySet())
classes.put(entry.getKey(), entry.getValue().getBytes());
}

return classes;
}

Class<?> loadAndReturnMainClass(String mainClassName, ThrowingBiFunction<String, byte[], Class<?>> definer) throws Exception {
Class<?> result = null;

for (Entry<String, byte[]> entry : classes().entrySet()) {
Class<?> c = definer.apply(entry.getKey(), entry.getValue());
if (mainClassName.equals(entry.getKey()))
result = c;
}

return result;
}
}

@FunctionalInterface
interface ThrowingBiFunction<T, U, R> {
R apply(T t, U u) throws Exception;
}

static final class CharSequenceJavaFileObject extends SimpleJavaFileObject {
Expand Down
18 changes: 18 additions & 0 deletions jOOR/src/test/java/org/joor/test/CompileTest.java
Expand Up @@ -155,6 +155,24 @@ public void testCompileNestedClass() {
int foo = Reflect.on(c).create().call("inner").call("foo").get();
assertEquals(42, foo);
}

@Test
public void testCompileTopLevelClasses() {
Class<?> c =
Reflect.compile(
"org.joor.test.CompileTopLevelClasses",
"package org.joor.test;" +
"public class CompileTopLevelClasses {" +
"Other other() { return new Other(); }" +
"}" +
"class Other {" +
"int foo() { return 42; }" +
"}")
.get();

int foo = Reflect.on(c).create().call("other").call("foo").get();
assertEquals(42, foo);
}
}

interface I extends J {
Expand Down

0 comments on commit fb03168

Please sign in to comment.