-
Notifications
You must be signed in to change notification settings - Fork 1
/
AbstractClassLoader.java
167 lines (156 loc) · 6.17 KB
/
AbstractClassLoader.java
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
package com.github.sanctum.labyrinth.data;
import com.github.sanctum.labyrinth.LabyrinthProvider;
import com.github.sanctum.labyrinth.annotation.Experimental;
import com.github.sanctum.labyrinth.library.EasyTypeAdapter;
import com.github.sanctum.labyrinth.library.TypeFlag;
import com.google.common.collect.ImmutableList;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import org.bukkit.plugin.Plugin;
/**
* A class dedicated to allowing developers to load external jars into memory.
*
* @param <T> The optional main class this loader needs to locate and instantiate.
*/
public abstract class AbstractClassLoader<T> extends URLClassLoader {
protected final Field PLUGIN_CLASS_MAP;
protected final List<Class<?>> classes;
protected final ClassLoader pluginClassLoader;
protected final T mainClass;
protected AbstractClassLoader(File file, ClassLoader parent, Object... args) throws IOException {
super(new URL[]{file.toURI().toURL()}, parent);
TypeFlag<T> flag = new EasyTypeAdapter<>();
Class<T> main = flag.getType();
try {
PLUGIN_CLASS_MAP = Class.forName("org.bukkit.plugin.java.PluginClassLoader").getDeclaredField("classes");
PLUGIN_CLASS_MAP.setAccessible(true);
} catch (NoSuchFieldException | ClassNotFoundException e) {
throw new IllegalStateException("Unable to reach plugin class map", e);
}
final List<Class<?>> loadedClasses = new ArrayList<>();
final Plugin plugin = LabyrinthProvider.getInstance().getPluginInstance();
this.pluginClassLoader = plugin.getClass().getClassLoader();
if (!file.isFile()) throw new IllegalArgumentException("The provided file is not a jar file!");
new JarFile(file).stream()
.map(ZipEntry::getName)
.filter(entryName -> entryName.contains(".class") && !entryName.contains("$"))
.map(classPath -> classPath.replace('/', '.'))
.map(className -> className.substring(0, className.length() - 6))
.forEach(s -> {
final Class<?> resolvedClass;
try {
resolvedClass = loadClass(s, true);
} catch (ClassNotFoundException e) {
plugin.getLogger().warning(() -> "Unable to inject '" + s + "'");
plugin.getLogger().warning(e::getMessage);
return;
}
getPluginClassMap().put(s, resolvedClass);
plugin.getLogger().finest(() -> "Loaded '" + s + "' successfully.");
loadedClasses.add(resolvedClass);
});
this.classes = loadedClasses;
if (main != null) {
try {
Class<? extends T> addonClass = loadedClasses.stream().filter(main::isAssignableFrom).findFirst().map(aClass -> (Class<? extends T>) aClass).get();
if (args != null) {
Constructor<T> constructor = null;
for (Constructor<?> con : main.getConstructors()) {
if (args.length == con.getParameters().length) {
int success = 0;
for (int i = 0; i < args.length; i++) {
Class<?> objectClass = args[i].getClass();
Class<?> typeClass = con.getParameters()[i].getType();
if (objectClass.isAssignableFrom(typeClass)) {
success++;
}
if (success == args.length) {
constructor = (Constructor<T>) con;
break;
}
}
}
}
this.mainClass = constructor != null ? constructor.newInstance(args) : addonClass.getDeclaredConstructor().newInstance();
} else {
this.mainClass = addonClass.getDeclaredConstructor().newInstance();
}
} catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException ex) {
throw new IllegalStateException("No public constructor", ex);
} catch (InstantiationException ex) {
throw new IllegalStateException("Unusual constructor args detected.", ex);
}
} else this.mainClass = null;
}
/**
* Get the main class for this class loader.
*
* @return the main class for this class loader if one exists.
*/
public T getMainClass() {
return mainClass;
}
/**
* Get a list of all classes loaded by this class loader.
*
* @return all classes loaded by this class loader.
*/
public List<Class<?>> getClasses() {
return ImmutableList.copyOf(classes);
}
/**
* Unload a class from memory. If the provided class is not found an exception will occur, if the provided string results in a path
* this method will switch in an attempt at locating and removing the relative class files it belongs to.
*
* @param name The name of the class file or path.
* @return true if the class(es) got removed from memory.
* @throws ClassNotFoundException if the attempted class resolve fails and the included text doesn't result in a valid directory.
*/
@Experimental
public boolean unload(String name) throws ClassNotFoundException {
Map<String, Class<?>> classes = getPluginClassMap();
if (classes.containsKey(name)) {
classes.remove(name);
return true;
} else throw new ClassNotFoundException("Class " + name + " not found, cannot unload.");
}
/**
* Simply unload a loaded class from this addon loader.
*
* @param clazz The class to unload.
* @throws WrongLoaderUsedException when the class attempting removal belongs to a different loader instance.
* @return true if the class was able to unload.
*/
@Experimental
public boolean unload(Class<?> clazz) throws WrongLoaderUsedException {
Map<String, Class<?>> classes = getPluginClassMap();
String name = clazz.getName().replace("/", ".").substring(0, clazz.getName().length() - 6);
classes.remove(name);
if (!this.classes.contains(clazz)) throw new WrongLoaderUsedException("Class " + clazz.getName() + " does not belong to this loader!");
return this.classes.remove(clazz);
}
public final Map<String, Class<?>> getPluginClassMap() throws IllegalStateException {
try {
//noinspection unchecked
return (Map<String, Class<?>>) PLUGIN_CLASS_MAP.get(this.pluginClassLoader);
} catch (ClassCastException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
@Override
public String toString() {
return "AbstractFileLoader{" +
"Main=" + mainClass +
'}';
}
}