Skip to content

Instantly share code, notes, and snippets.

@niloc132
Last active April 25, 2025 15:15
Show Gist options
  • Save niloc132/adfcb1c507a5561782425229df4cd4df to your computer and use it in GitHub Desktop.
Save niloc132/adfcb1c507a5561782425229df4cd4df to your computer and use it in GitHub Desktop.
package com.vertispan.tools;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.TypeSpec;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URLClassLoader;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* Takes a list of jars, packages/classes found in them, and output package and directory. Generates a stub
* implementation of each interface found. Current implementation uses a separate classloader to handle the
* jars, but in theory could be implemented with type elements/mirrors to avoid even needing dependencies
* of the jar that contains the relevant types.
*/
public class NotImplementedYet {
public static void main(String[] args) throws Exception {
URLClassLoader classloader = createClassloader(args[0].split(Pattern.quote(File.pathSeparator)));
Set<String> packagesAndTypes = Set.copyOf(Arrays.asList(args[1].split(Pattern.quote(File.pathSeparator))));
String outputPackage = args[2];
String outputPath = args[3];
Files.createDirectories(Path.of(outputPath));
Set<String> interfaces = collectInterfaces(classloader, packagesAndTypes);
processInterfaces(classloader, interfaces, outputPackage, outputPath, "Stub");
}
private static URLClassLoader createClassloader(String[] classpathStrings) throws MalformedURLException {
URL[] classpathElements = new URL[classpathStrings.length];
for (int i = 0; i < classpathStrings.length; i++) {
classpathElements[i] = new File(classpathStrings[i]).toURL();
}
return new URLClassLoader(classpathElements);
}
/**
* Simple classloader-based implementation, allowing us both to walk directories to match our desired
* paths, and also to later load classes from them.
*/
private static Set<String> collectInterfaces(URLClassLoader classLoader, Set<String> packagesAndTypes) throws IOException {
// copy the requested set, so we can modify it
Set<String> remainingPackagesAndTypes = new HashSet<>(packagesAndTypes);
Set<String> foundInterfaces = new HashSet<>();
// for each specified type, see if it can be loaded directly from the classloader and is an interface
Set.copyOf(remainingPackagesAndTypes).forEach(packageOrType -> {
try {
Class<?> found = classLoader.loadClass(packageOrType);
if (found.isInterface()) {
remainingPackagesAndTypes.remove(packageOrType);
foundInterfaces.add(packageOrType);
}
} catch (ClassNotFoundException ignore) {
// ignore, probably a package
}
});
// for each jar on the classloader, see if it contains classes in the specified packages
Set<String> foundPackages = new HashSet<>();
for (URL url : classLoader.getURLs()) {
if (url.getFile().endsWith(".jar")) {
try (JarInputStream is = new JarInputStream(url.openStream())) {
for (JarEntry entry = is.getNextJarEntry(); entry != null; entry = is.getNextJarEntry()) {
String name = entry.getName();
int pos = name.lastIndexOf("/");
if (pos <= 0) {
// ignore top-level files of any kind
continue;
}
String packageName = name.substring(0, pos).replaceAll("/", ".");
if (name.endsWith(".class") && remainingPackagesAndTypes.contains(packageName)) {
try {
String typeName = name.substring(0, name.length() - ".class".length()).replaceAll("/", ".");
Class<?> found = classLoader.loadClass(typeName);
if (found.isInterface()) {
foundInterfaces.add(typeName);
foundPackages.add(packageName);
}
} catch (ClassNotFoundException ignored) {
// nevermind, wasn't a class we could load
}
}
}
}
} else {
//TODO handle directories of class files?
System.err.println("Ignoring classpath element" + url);
}
}
remainingPackagesAndTypes.removeAll(foundPackages);
if (!remainingPackagesAndTypes.isEmpty()) {
System.err.println("Failed to find package/interface entries:");
remainingPackagesAndTypes.forEach(e -> System.err.println("\t" + e));
}
return foundInterfaces;
}
private static void processInterfaces(URLClassLoader classloader, Set<String> interfaces, String outputPackage, String outputPath, String prefix) throws ClassNotFoundException, IOException {
for (String anInterface : interfaces) {
Class<?> loadedInterface = classloader.loadClass(anInterface);
ClassName interfaceName = ClassName.get(loadedInterface);
TypeSpec.Builder typeSpec = TypeSpec.classBuilder(prefix + interfaceName.simpleName())
.addSuperinterface(interfaceName)
.addModifiers(javax.lang.model.element.Modifier.PUBLIC);
for (Method method : loadedInterface.getMethods()) {
if (Modifier.isAbstract(method.getModifiers())) {
typeSpec.addMethod(MethodSpec.methodBuilder(method.getName())
.addModifiers(javax.lang.model.element.Modifier.PUBLIC)
.returns(method.getReturnType())
.addParameters(
Arrays.stream(method.getParameters())
.map(p -> ParameterSpec.builder(p.getType(), p.getName()).build())
.collect(Collectors.toList())
)
.addStatement("throw new UnsupportedOperationException($S)", method.getName())
.build());
}
}
JavaFile javaFile = JavaFile.builder(outputPackage, typeSpec.build()).build();
javaFile.writeTo(Path.of(outputPath));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment