aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/moe/nea/firmament/init/AutoDiscoveryPlugin.java
blob: e3644c0f82945ed5263a06720a6da8b244d9e6f4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
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);
        }
    }

}