diff options
Diffstat (limited to 'src/main/java/moe/nea/firmament/init/AutoDiscoveryPlugin.java')
-rw-r--r-- | src/main/java/moe/nea/firmament/init/AutoDiscoveryPlugin.java | 173 |
1 files changed, 173 insertions, 0 deletions
diff --git a/src/main/java/moe/nea/firmament/init/AutoDiscoveryPlugin.java b/src/main/java/moe/nea/firmament/init/AutoDiscoveryPlugin.java new file mode 100644 index 0000000..e3644c0 --- /dev/null +++ b/src/main/java/moe/nea/firmament/init/AutoDiscoveryPlugin.java @@ -0,0 +1,173 @@ +package moe.nea.firmament.init; + + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +public class AutoDiscoveryPlugin { + private static final List<AutoDiscoveryPlugin> mixinPlugins = new ArrayList<>(); + + public static List<AutoDiscoveryPlugin> getMixinPlugins() { + return mixinPlugins; + } + + private String mixinPackage; + + public void setMixinPackage(String mixinPackage) { + this.mixinPackage = mixinPackage; + mixinPlugins.add(this); + } + + /** + * Resolves the base class root for a given class URL. This resolves either the JAR root, or the class file root. + * In either case the return value of this + the class name will resolve back to the original class url, or to other + * class urls for other classes. + */ + public URL getBaseUrlForClassUrl(URL classUrl) { + String string = classUrl.toString(); + if (classUrl.getProtocol().equals("jar")) { + try { + return new URL(string.substring(4).split("!")[0]); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + if (string.endsWith(".class")) { + try { + return new URL(string.replace("\\", "/") + .replace(getClass().getCanonicalName() + .replace(".", "/") + ".class", "")); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + return classUrl; + } + + /** + * Get the package that contains all the mixins. This value is set using {@link #setMixinPackage}. + */ + public String getMixinPackage() { + return mixinPackage; + } + + /** + * Get the path inside the class root to the mixin package + */ + public String getMixinBaseDir() { + return mixinPackage.replace(".", "/"); + } + + /** + * A list of all discovered mixins. + */ + private List<String> mixins = null; + + /** + * Try to add mixin class ot the mixins based on the filepath inside of the class root. + * Removes the {@code .class} file suffix, as well as the base mixin package. + * <p><b>This method cannot be called after mixin initialization.</p> + * + * @param className the name or path of a class to be registered as a mixin. + */ + public void tryAddMixinClass(String className) { + if (!className.endsWith(".class")) return; + String norm = (className.substring(0, className.length() - ".class".length())) + .replace("\\", "/") + .replace("/", "."); + if (norm.startsWith(getMixinPackage() + ".") && !norm.endsWith(".")) { + mixins.add(norm.substring(getMixinPackage().length() + 1)); + } + } + + private void tryDiscoverFromContentFile(URL url) { + Path file; + try { + file = Paths.get(getBaseUrlForClassUrl(url).toURI()); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + System.out.println("Base directory found at " + file); + if (!Files.exists(file)) { + System.out.println("Skipping non-existing mixin root: " + file); + return; + } + if (Files.isDirectory(file)) { + walkDir(file); + } else { + walkJar(file); + } + System.out.println("Found mixins: " + mixins); + + } + + /** + * Search through the JAR or class directory to find mixins contained in {@link #getMixinPackage()} + */ + public List<String> getMixins() { + if (mixins != null) return mixins; + System.out.println("Trying to discover mixins"); + mixins = new ArrayList<>(); + URL classUrl = getClass().getProtectionDomain().getCodeSource().getLocation(); + System.out.println("Found classes at " + classUrl); + tryDiscoverFromContentFile(classUrl); + var classRoots = System.getProperty("firmament.classroots"); + if (classRoots != null && !classRoots.isBlank()) { + System.out.println("Found firmament class roots: " + classRoots); + for (String s : classRoots.split(File.pathSeparator)) { + if (s.isBlank()) { + continue; + } + try { + tryDiscoverFromContentFile(new File(s).toURI().toURL()); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + } + return mixins; + } + + /** + * Search through directory for mixin classes based on {@link #getMixinBaseDir}. + * + * @param classRoot The root directory in which classes are stored for the default package. + */ + private void walkDir(Path classRoot) { + System.out.println("Trying to find mixins from directory"); + try (Stream<Path> classes = Files.walk(classRoot.resolve(getMixinBaseDir()))) { + classes.map(it -> classRoot.relativize(it).toString()) + .forEach(this::tryAddMixinClass); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Read through a JAR file, trying to find all mixins inside. + */ + private void walkJar(Path file) { + System.out.println("Trying to find mixins from jar file"); + try (ZipInputStream zis = new ZipInputStream(Files.newInputStream(file))) { + ZipEntry next; + while ((next = zis.getNextEntry()) != null) { + tryAddMixinClass(next.getName()); + zis.closeEntry(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +} |