Last active
April 25, 2025 15:15
-
-
Save niloc132/adfcb1c507a5561782425229df4cd4df to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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