Skip to content

Commit

Permalink
Add stubjars constructor expression array
Browse files Browse the repository at this point in the history
  • Loading branch information
dmssargent committed Aug 11, 2024
1 parent f27c4f4 commit 50a7e66
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 54 deletions.
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ checkstyle {
}

group 'gent.davidsar.stubjars'
version '0.3.4-SNAPSHOT'
version '0.3.4'


sourceCompatibility = 1.11
Expand Down Expand Up @@ -46,7 +46,7 @@ dependencies {
errorprone 'com.google.errorprone:error_prone_core:2.18.0'
testImplementation group: 'junit', name: 'junit', version: '4.13.2'
testImplementation 'org.assertj:assertj-core:3.25.1'
implementation 'ch.qos.logback:logback-classic:1.4.6'
implementation 'ch.qos.logback:logback-classic:1.4.12'
implementation 'org.jetbrains:annotations:24.0.1'
}

Expand Down
40 changes: 21 additions & 19 deletions src/main/java/davidsar/gent/stubjars/JarFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
Expand Down Expand Up @@ -53,8 +54,8 @@ static JarFile forFile(@NotNull File jar) {
}

@NotNull
static ClassLoader createClassLoaderFromJars(@Nullable ClassLoader parentClassLoader, JarFile... jarFiles) {
URL[] urls = Arrays.stream(jarFiles).map(JarFile::getUrl).toArray(URL[]::new);
static ClassLoader createClassLoaderFromJars(@Nullable ClassLoader parentClassLoader, String... jarFiles) {
URL[] urls = Arrays.stream(jarFiles).map(File::new).map(JarFile::forFile).map(JarFile::getUrl).toArray(URL[]::new);
ClassLoader classLoader = parentClassLoader == null ? JarFile.class.getClassLoader() : parentClassLoader;
return new URLClassLoader(urls, classLoader);
}
Expand All @@ -69,23 +70,24 @@ private URL getUrl() {
}

Set<JarClass<?>> getClasses(ClassLoader loader) throws IOException {
java.util.jar.JarFile iJar = new java.util.jar.JarFile(jar);
return Streams.makeFor(iJar.entries())
.filter(jarEntry -> jarEntry.getName().endsWith(".class"))
.map(entry -> {
try {
return new JarClass<>(loader, entry.getName());
} catch (ClassNotFoundException e) {
log.error("unable to load class: not found: ignored: " + entry.getName());
return null;
} catch (LinkageError e) {
log.error("unable to load class: linkage error: ignored: " + entry.getName());
return null;
}
})
.filter(clazz -> clazz != null)
.flatMap(clazz -> Stream.concat(Stream.of(clazz), findInnerClasses(clazz)))
.collect(Collectors.toSet());
try (java.util.jar.JarFile iJar = new java.util.jar.JarFile(jar)) {
return Streams.makeFor(iJar.entries())
.filter(jarEntry -> jarEntry.getName().endsWith(".class"))
.map(entry -> {
try {
return new JarClass<>(loader, entry.getName());
} catch (ClassNotFoundException e) {
log.error("unable to load class: not found: ignored: " + entry.getName());
return null;
} catch (LinkageError e) {
log.error("unable to load class: linkage error: ignored: " + entry.getName());
return null;
}
})
.filter(Objects::nonNull)
.flatMap(clazz -> Stream.concat(Stream.of(clazz), findInnerClasses(clazz)))
.collect(Collectors.toSet());
}
}

@NotNull
Expand Down
33 changes: 19 additions & 14 deletions src/main/java/davidsar/gent/stubjars/StubJars.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeMap;
Expand Down Expand Up @@ -61,7 +63,7 @@ public class StubJars {
private static final File BUILD_DIR = new File(SOURCE_DIR, "build");
private static final File CLASSES_DIR = new File(BUILD_DIR, "classes");
private static final File SOURCES_LIST_FILE = new File(SOURCE_DIR, "sources.list");
private final int numberOfCompilerThreads = Math.min(Runtime.getRuntime().availableProcessors() - 1, 1);
private final int numberOfCompilerThreads = Math.max(Runtime.getRuntime().availableProcessors() - 1, 1);


private StubJars(@NotNull List<JarClass<?>> clazzes, List<JarFile> classpathJars) {
Expand Down Expand Up @@ -287,12 +289,12 @@ public void generateJarForGeneratedCode() throws IOException, InterruptedExcepti
* Creates new {@link StubJars} instances.
*/
static class Builder {
private final List<JarFile> jars;
private final List<JarFile> classpathJars;
private final Set<String> jars;
private final Set<String> classpathJars;

private Builder() {
jars = new ArrayList<>();
classpathJars = new ArrayList<>();
jars = new LinkedHashSet<>();
classpathJars = new LinkedHashSet<>();
}

/**
Expand All @@ -301,8 +303,9 @@ private Builder() {
* @param jar a {@link File} representing a JAR file
*/
void addJar(@NotNull File jar) {
log.info("adding jar: " + jar.getAbsolutePath());
jars.add(JarFile.forFile(jar));
if (jars.add(jar.getPath())) {
log.info("adding jar: {}", jar.getAbsolutePath());
}
}

/**
Expand All @@ -314,8 +317,9 @@ void addClasspathJar(@NotNull File jar) throws IOException {
if (!jar.exists()) {
throw new IOException("A provided classpath JAR doesn't exist. File: " + jar.getAbsolutePath());
}
log.info("adding classPathJar: " + jar.getAbsolutePath());
classpathJars.add(JarFile.forFile(jar));
if (classpathJars.add(jar.getPath())) {
log.info("adding classPathJar: {}", jar.getAbsolutePath());
}
}

/**
Expand Down Expand Up @@ -373,11 +377,12 @@ void addJarsAndAars(@NotNull File... jarsAndAars) throws IOException {
* @return a new {@link StubJars} instance
*/
@NotNull StubJars build() {
ClassLoader cpClassLoader = JarFile.createClassLoaderFromJars(null, classpathJars.toArray(new JarFile[0]));
ClassLoader classLoader = JarFile.createClassLoaderFromJars(cpClassLoader, jars.toArray(new JarFile[0]));
ClassLoader cpClassLoader = JarFile.createClassLoaderFromJars(null, classpathJars.toArray(new String[0]));
ClassLoader classLoader = JarFile.createClassLoaderFromJars(cpClassLoader, jars.toArray(new String[0]));
List<JarClass<?>> clazzes = Collections.synchronizedList(new ArrayList<>());
for (JarFile jar : jars) {
log.info("loading jar: " + jar.getJar().getAbsolutePath());
for (String jarPath : jars) {
JarFile jar = JarFile.forFile(new File(jarPath));
log.info("loading jar: {}", jar.getJar().getAbsolutePath());
final Set<JarClass<?>> classes;
try {
classes = jar.getClasses(classLoader);
Expand All @@ -389,7 +394,7 @@ void addJarsAndAars(@NotNull File... jarsAndAars) throws IOException {
}

JarClass.loadJarClassList(clazzes);
return new StubJars(clazzes, classpathJars);
return new StubJars(clazzes, classpathJars.stream().map(File::new).map(JarFile::forFile).collect(Collectors.toList()));
}
}

Expand Down
42 changes: 25 additions & 17 deletions src/main/java/davidsar/gent/stubjars/components/JarClass.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public class JarClass<T> extends JarModifiers implements CompileableExpression {
private static Map<String, JarClass<?>> classToJarClassMap;

private Class<T> clazz;

private final ClassLoader stubClassLoader;
private Map<String, JarConstructor> constructors;
private Map<String, JarMethod> methods;
Expand All @@ -76,9 +77,9 @@ public JarClass(@NotNull ClassLoader classLoader, @NotNull String entryName) thr
stubClassLoader = Objects.requireNonNull(classLoader);
}

private JarClass(@NotNull Class<T> clazz) {
private JarClass(@NotNull Class<T> clazz, ClassLoader classLoader) {
this.clazz = clazz;
this.stubClassLoader = clazz.getClassLoader();
this.stubClassLoader = classLoader;
}

public static void loadJarClassList(@NotNull List<JarClass<?>> list) {
Expand All @@ -93,12 +94,12 @@ public static void loadJarClassList(@NotNull List<JarClass<?>> list) {
}

@NotNull
static <T> JarClass<?> forClass(@NotNull Class<T> clazz) {
static <T> JarClass<?> forClass(@NotNull Class<T> clazz, ClassLoader parentClassLoader) {
if (classToJarClassMap != null && classToJarClassMap.containsKey(clazz.getName())) {
return classToJarClassMap.get(clazz.getName());
}

return new JarClass<>(clazz);
return new JarClass<>(clazz, Objects.requireNonNull(parentClassLoader));
}

@NotNull
Expand Down Expand Up @@ -217,7 +218,7 @@ public Map<String, JarClass<?>> innerClasses() {
innerClasses = Arrays.stream(clazz.getDeclaredClasses())
.filter(clazz -> !clazz.isLocalClass())
.filter(clazz -> !clazz.isAnonymousClass())
.map(JarClass::forClass).collect(Collectors.toMap(x -> x.clazz.getName(), Function.identity(), (x, y) -> y, TreeMap::new));
.map((Class<?> clazz1) -> forClass(clazz1, stubClassLoader)).collect(Collectors.toMap(x -> x.clazz.getName(), Function.identity(), (x, y) -> y, TreeMap::new));
}

return innerClasses;
Expand Down Expand Up @@ -247,7 +248,7 @@ public int hashCode() {
}

@NotNull Map<String, JarClass> allSuperClassesAndInterfaces() {
return allSuperClassesAndInterfaces(clazz).stream().map(JarClass::forClass).collect(Collectors.toMap(x -> x.clazz.getName(), Function.identity(), (x, y) -> y, TreeMap::new));
return allSuperClassesAndInterfaces(clazz).stream().map((Class<?> clazz1) -> forClass(clazz1, stubClassLoader)).collect(Collectors.toMap(x -> x.clazz.getName(), Function.identity(), (x, y) -> y, TreeMap::new));
}

@NotNull
Expand Down Expand Up @@ -315,16 +316,19 @@ public Expression compileToExpression() {
try {
return compileClass(false, null);
} catch (NoClassDefFoundError ex) {
try {
//noinspection unchecked
Objects.requireNonNull(stubClassLoader);
Objects.requireNonNull(fullName());
clazz = (Class<T>) Class.forName(fullName(), false, new URLClassLoader(((URLClassLoader) stubClassLoader).getURLs(), stubClassLoader.getParent()));
return compileClass(false, null);
} catch (ClassNotFoundException | NoClassDefFoundError e) {
log.warn("Missing class definition for {}", fullName(), e);
return StringExpression.EMPTY;
}
log.warn("Missing class definition for {}. ClassLoader was: {}", fullName(), ((URLClassLoader) stubClassLoader).getURLs());
log.warn("Error was: ", ex);
return StringExpression.EMPTY;
// try {
// //noinspection unchecked
// Objects.requireNonNull(stubClassLoader);
// Objects.requireNonNull(fullName());
// clazz = (Class<T>) Class.forName(fullName(), false, new URLClassLoader(((URLClassLoader) stubClassLoader).getURLs(), stubClassLoader.getParent()));
// return compileClass(false, null);
// } catch (ClassNotFoundException | NoClassDefFoundError e) {
// log.warn("Missing class definition for {}", fullName(), e);
// return StringExpression.EMPTY;
// }
}
}

Expand Down Expand Up @@ -353,7 +357,7 @@ private Expression handleEnumClass(Expression methods, Expression fields, Expres

if (invokedExpression != null) {
enumMembers = new EnumMembers(Arrays.stream(invokedExpression)
.map(member -> JarClass.forClass(member.getClass()).compileClass(true, member.name()))
.map(member -> JarClass.forClass(member.getClass(), getClassLoader()).compileClass(true, member.name()))
.toArray(Expression[]::new)).asStatement();
}

Expand Down Expand Up @@ -513,6 +517,10 @@ static <T extends Enum> T[] getEnumConstantsFor(@NotNull Class<T> clazz) {
}
}

public ClassLoader getClassLoader() {
return stubClassLoader;
}

private static class ClassExpression extends Expression {
private final List<Expression> children;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ private Expression determineBody() {
} else {
// We need to call some form of the default constructor, so we can compile code
JarConstructor<?>[] declaredConstructors;
JarClass<?> jarClass = JarClass.forClass(clazzSuperClass);
JarClass<?> jarClass = JarClass.forClass(clazzSuperClass, clazz.getClassLoader());
declaredConstructors = jarClass.constructors().values().toArray(new JarConstructor<?>[0]);
if (declaredConstructors.length == 0) {
throw new UnsupportedOperationException("Cannot infer super cotr to write for " + clazz.fullName());
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/logback.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
</encoder>
</appender>

<root level="debug">
<root level="info">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
44 changes: 44 additions & 0 deletions src/test/java/davidsar/gent/stubjars/components/JarClassTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
package davidsar.gent.stubjars.components;


import davidsar.gent.stubjars.components.expressions.Expression;
import davidsar.gent.stubjars.components.expressions.Expressions;
import davidsar.gent.stubjars.components.expressions.PackageStatement;
import davidsar.gent.stubjars.components.expressions.StringExpression;
import davidsar.gent.stubjars.components.writer.Constants;
import davidsar.gent.stubjars.components.writer.JavaClassWriter;
import org.junit.Test;

import static org.assertj.core.api.Assertions.assertThat;
Expand Down Expand Up @@ -33,4 +39,42 @@ public void testImplementationIsWritten() throws ClassNotFoundException {
" public void testMethod() {}\n" +
" }\n");
}

@Test
public void fieldDeclarationArrayWorks() throws ClassNotFoundException {
var testInterfaceClass = new JarClass<TestConstructorClass>(JarClassTest.class.getClassLoader(), TestConstructorClass.class.getName());
assertThat(testInterfaceClass.isInterface()).isFalse();
assertThat(testInterfaceClass.isEnum()).isFalse();
assertThat(testInterfaceClass.isInnerClass()).isFalse();

assertThat(testInterfaceClass.compileToExpression().toString()).isEqualTo(
"public class TestConstructorClass {\n" +
" public TestConstructorClass() {}\n" +
" public enum ColorSwatch {\n" +
" RED{\n" +
" }\n" +
",\n" +
"GREEN{\n" +
" }\n" +
",\n" +
"BLUE{\n" +
" }\n" +
";\n" +
" }\n" +
"public static class Result {\n" +
" public final davidsar.gent.stubjars.components.TestConstructorClass.ColorSwatch closestSwatch = davidsar.gent.stubjars.components.TestConstructorClass.ColorSwatch.RED;\n" +
"public final int[] rgb = new int[] {};\n" +
" public Result(davidsar.gent.stubjars.components.TestConstructorClass.ColorSwatch arg0, float[] arg1) {}\n" +
" }\n" +
"public static abstract class ResultGetter {\n" +
" public ResultGetter() {}\n" +
" public abstract davidsar.gent.stubjars.components.TestConstructorClass.Result getAnalysis();\n" +
" }\n" +
"}\n");
Expression packageStatement = new PackageStatement(testInterfaceClass.packageName());
Expression classBody = testInterfaceClass.compileToExpression();
Expression fullClass = Expressions.of(packageStatement, StringExpression.NEW_LINE, classBody);
var result = String.join(Constants.NEW_LINE_CHARACTER, TreeFormatter.toLines(fullClass));
assertThat(result).isNotEmpty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package davidsar.gent.stubjars.components;

public class TestConstructorClass {
public static abstract class ResultGetter {
public abstract Result getAnalysis();
}

public enum ColorSwatch {
RED, GREEN, BLUE
}

public static class Result
{
public final ColorSwatch closestSwatch;
public final int[] rgb = new int[3];

public Result(ColorSwatch closestSwatch, float[] hsv)
{
this.closestSwatch = closestSwatch;
}
}
}

0 comments on commit 50a7e66

Please sign in to comment.