Skip to content

Commit c21832c

Browse files
committed
Better compile time reflection and refactoring
1 parent b124a2c commit c21832c

File tree

10 files changed

+159
-80
lines changed

10 files changed

+159
-80
lines changed

annotations/src/main/java/com/javadiscord/jdi/core/processor/loader/ComponentLoader.java

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,9 @@ public class ComponentLoader {
2020
private static final Map<Class<?>, Object> COMPONENTS = new HashMap<>();
2121
private final ComponentValidator componentValidator = new ComponentValidator();
2222

23-
public ComponentLoader() {
24-
try {
25-
loadComponents();
26-
} catch (Exception e) {
27-
LOGGER.error("An error occurred while loading components classes", e);
28-
}
29-
}
23+
public ComponentLoader() {}
3024

31-
private void loadComponents() {
25+
public void loadComponents() {
3226
List<File> classes = ClassFileUtil.getClassesInClassPath();
3327
for (File classFile : classes) {
3428
try {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.javadiscord.jdi.core;
2+
3+
public class Constants {
4+
public static final String COMMAND_OPTION_CHOICE_ANNOTATION =
5+
"com.javadiscord.jdi.core.annotations.CommandOptionChoice";
6+
public static final String LISTENER_LOADER_CLASS =
7+
"com.javadiscord.jdi.core.processor.loader.ListenerLoader";
8+
public static final String COMPONENT_LOADER_CLASS =
9+
"com.javadiscord.jdi.core.processor.loader.ComponentLoader";
10+
public static final String SLASH_COMMAND_LOADER_CLASS =
11+
"com.javadiscord.jdi.core.processor.loader.SlashCommandLoader";
12+
13+
}

core/src/main/java/com/javadiscord/jdi/core/Discord.java

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
import com.javadiscord.jdi.core.api.builders.command.CommandOptionType;
2323
import com.javadiscord.jdi.core.interaction.InteractionEventHandler;
2424
import com.javadiscord.jdi.core.models.ready.ReadyEvent;
25+
import com.javadiscord.jdi.internal.ReflectiveComponentLoader;
26+
import com.javadiscord.jdi.internal.ReflectiveLoader;
27+
import com.javadiscord.jdi.internal.ReflectiveSlashCommandClassMethod;
2528
import com.javadiscord.jdi.internal.api.DiscordRequest;
2629
import com.javadiscord.jdi.internal.api.DiscordRequestDispatcher;
2730
import com.javadiscord.jdi.internal.api.DiscordResponseFuture;
@@ -137,10 +140,11 @@ private void registerLoadedAnnotationsWithDiscord() {
137140
LOGGER.info("Registering slash commands with Discord");
138141
loadedSlashCommands.forEach((commandName, slashCommandClassInstance) -> {
139142
try {
140-
Class<?> slashCommandClassInstanceClass = slashCommandClassInstance.getClass();
141-
Method method =
142-
(Method) slashCommandClassInstanceClass.getMethod("method")
143-
.invoke(slashCommandClassInstance);
143+
ReflectiveSlashCommandClassMethod slashCommandClassMethod =
144+
ReflectiveLoader
145+
.proxy(slashCommandClassInstance, ReflectiveSlashCommandClassMethod.class);
146+
147+
Method method = slashCommandClassMethod.method();
144148

145149
Annotation[] annotations = method.getAnnotations();
146150
for (Annotation annotation : annotations) {
@@ -216,7 +220,7 @@ private void addCommandOptionChoice(
216220
Annotation annotation1 = (Annotation) choice;
217221
if (
218222
annotation1.annotationType().getName()
219-
.equals("com.javadiscord.jdi.core.annotations.CommandOptionChoice")
223+
.equals(Constants.COMMAND_OPTION_CHOICE_ANNOTATION)
220224
) {
221225
Method nameMethod1 = annotation1.annotationType().getMethod("name");
222226
Method valueMethod1 = annotation1.annotationType().getMethod("value");
@@ -228,7 +232,7 @@ private void addCommandOptionChoice(
228232

229233
private boolean annotationLibPresent() {
230234
try {
231-
Class.forName("com.javadiscord.jdi.core.processor.loader.ListenerLoader");
235+
Class.forName(Constants.LISTENER_LOADER_CLASS);
232236
return true;
233237
} catch (Exception e) {
234238
return false;
@@ -239,9 +243,19 @@ private void loadComponents() {
239243
LOGGER.info("Loading Components");
240244
try {
241245
Class<?> clazz =
242-
Class.forName("com.javadiscord.jdi.core.processor.loader.ComponentLoader");
246+
Class.forName(Constants.COMPONENT_LOADER_CLASS);
247+
ReflectiveComponentLoader componentLoader = null;
243248
for (Constructor<?> constructor : clazz.getConstructors()) {
244-
constructor.newInstance();
249+
if (constructor.getParameterCount() == 0) {
250+
componentLoader =
251+
ReflectiveLoader
252+
.proxy(constructor.newInstance(), ReflectiveComponentLoader.class);
253+
}
254+
}
255+
if (componentLoader != null) {
256+
componentLoader.loadComponents();
257+
} else {
258+
throw new RuntimeException("Unable to create ComponentLoader instance");
245259
}
246260
} catch (Exception | Error e) {
247261
LOGGER.warn("Component loading failed", e);
@@ -252,7 +266,7 @@ private void loadAnnotations() {
252266
LOGGER.info("Loading EventListeners");
253267
try {
254268
Class<?> clazz =
255-
Class.forName("com.javadiscord.jdi.core.processor.loader.ListenerLoader");
269+
Class.forName(Constants.LISTENER_LOADER_CLASS);
256270
for (Constructor<?> constructor : clazz.getConstructors()) {
257271
if (constructor.getParameterCount() == 1) {
258272
Parameter parameters = constructor.getParameters()[0];
@@ -271,7 +285,7 @@ private void loadSlashCommands() {
271285
LOGGER.info("Loading SlashCommands");
272286
try {
273287
Class<?> clazz =
274-
Class.forName("com.javadiscord.jdi.core.processor.loader.SlashCommandLoader");
288+
Class.forName(Constants.SLASH_COMMAND_LOADER_CLASS);
275289
for (Constructor<?> constructor : clazz.getConstructors()) {
276290
if (constructor.getParameterCount() == 1) {
277291
Parameter parameters = constructor.getParameters()[0];

core/src/main/java/com/javadiscord/jdi/core/GatewayEventListenerAnnotations.java

Lines changed: 58 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -212,56 +212,78 @@ public GatewayEventListenerAnnotations(Discord discord) {
212212
this.discord = discord;
213213
}
214214

215-
@SuppressWarnings("unchecked")
216215
@Override
217216
public void receive(EventType eventType, Object event) {
218-
if (!EVENT_TYPE_ANNOTATIONS.containsKey(eventType)) {
217+
if (!isEventTypeValid(eventType)) {
219218
return;
220219
}
221-
Class<? extends Annotation> annotationClass;
222-
try {
223-
annotationClass =
224-
(Class<? extends Annotation>) Class.forName(EVENT_TYPE_ANNOTATIONS.get(eventType));
225-
} catch (ClassNotFoundException e) {
220+
221+
Class<? extends Annotation> annotationClass = getAnnotationClass(eventType);
222+
if (annotationClass == null) {
226223
LOGGER.error("Could not find annotation binding for {}", eventType);
227224
return;
228225
}
226+
227+
invokeAnnotatedMethods(annotationClass, event);
228+
}
229+
230+
private boolean isEventTypeValid(EventType eventType) {
231+
return EVENT_TYPE_ANNOTATIONS.containsKey(eventType);
232+
}
233+
234+
@SuppressWarnings("unchecked")
235+
private Class<? extends Annotation> getAnnotationClass(EventType eventType) {
236+
try {
237+
return (Class<? extends Annotation>) Class
238+
.forName(EVENT_TYPE_ANNOTATIONS.get(eventType));
239+
} catch (ClassNotFoundException e) {
240+
return null;
241+
}
242+
}
243+
244+
private void invokeAnnotatedMethods(Class<? extends Annotation> annotationClass, Object event) {
229245
for (Object listener : discord.getAnnotatedEventListeners()) {
230246
Method[] methods = listener.getClass().getMethods();
231-
List<Object> paramOrder = new ArrayList<>();
232247
for (Method method : methods) {
233-
if (!method.isAnnotationPresent(annotationClass)) {
234-
continue;
248+
if (method.isAnnotationPresent(annotationClass)) {
249+
invokeMethod(listener, method, event);
235250
}
236-
Parameter[] parameters = method.getParameters();
237-
for (Parameter parameter : parameters) {
238-
if (parameter.getParameterizedType() == event.getClass()) {
239-
paramOrder.add(event);
240-
} else if (parameter.getParameterizedType() == Discord.class) {
241-
paramOrder.add(discord);
242-
} else if (parameter.getParameterizedType() == Guild.class) {
243-
Guild guild = GatewayEventListener.getGuild(discord, event);
244-
paramOrder.add(guild);
245-
}
246-
}
247-
try {
248-
if (paramOrder.size() != method.getParameterCount()) {
249-
throw new RuntimeException(
250-
"Bound "
251-
+ paramOrder.size()
252-
+ " parameters but expected "
253-
+ method.getParameterCount()
254-
);
255-
}
256-
LOGGER.trace("Invoking method {} with params {}", method.getName(), paramOrder);
251+
}
252+
}
253+
}
257254

258-
method.invoke(listener, paramOrder.toArray());
255+
private void invokeMethod(Object listener, Method method, Object event) {
256+
List<Object> paramOrder = getParamOrder(method, event);
257+
if (paramOrder.size() != method.getParameterCount()) {
258+
throw new RuntimeException(
259+
"Bound " + paramOrder.size() + " parameters but expected "
260+
+ method.getParameterCount()
261+
);
262+
}
259263

260-
} catch (Exception e) {
261-
LOGGER.error("Failed to invoke {}", method.getName(), e);
262-
throw new RuntimeException(e);
263-
}
264+
try {
265+
LOGGER.trace("Invoking method {} with params {}", method.getName(), paramOrder);
266+
method.invoke(listener, paramOrder.toArray());
267+
} catch (Exception e) {
268+
LOGGER.error("Failed to invoke {}", method.getName(), e);
269+
throw new RuntimeException(e);
270+
}
271+
}
272+
273+
private List<Object> getParamOrder(Method method, Object event) {
274+
List<Object> paramOrder = new ArrayList<>();
275+
Parameter[] parameters = method.getParameters();
276+
for (Parameter parameter : parameters) {
277+
if (parameter.getParameterizedType() == event.getClass()) {
278+
paramOrder.add(event);
279+
} else if (parameter.getParameterizedType() == Discord.class) {
280+
paramOrder.add(discord);
281+
} else if (parameter.getParameterizedType() == Guild.class) {
282+
Guild guild = GatewayEventListener.getGuild(discord, event);
283+
paramOrder.add(guild);
264284
}
265285
}
286+
return paramOrder;
266287
}
288+
267289
}

core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
import com.javadiscord.jdi.core.GatewayEventListener;
1010
import com.javadiscord.jdi.core.Guild;
1111
import com.javadiscord.jdi.core.models.guild.Interaction;
12+
import com.javadiscord.jdi.internal.ReflectiveLoader;
13+
import com.javadiscord.jdi.internal.ReflectiveSlashCommandClassMethod;
14+
import com.javadiscord.jdi.internal.ReflectiveSlashCommandLoader;
1215

1316
import org.apache.logging.log4j.LogManager;
1417
import org.apache.logging.log4j.Logger;
@@ -30,29 +33,21 @@ public void onInteractionCreate(Interaction interaction, Guild guild) {
3033
String command = interaction.data().name();
3134

3235
try {
33-
Class<?> slashCommandLoaderClass = slashCommandLoader.getClass();
36+
ReflectiveSlashCommandLoader reflectiveSlashCommandLoader =
37+
ReflectiveLoader.proxy(slashCommandLoader, ReflectiveSlashCommandLoader.class);
3438

35-
Method getSlashCommandClassMethod =
36-
slashCommandLoaderClass.getMethod("getSlashCommandClassMethod", String.class);
37-
38-
Object commandClassMethodInstance =
39-
getSlashCommandClassMethod.invoke(slashCommandLoader, command);
40-
41-
if (commandClassMethodInstance == null) {
42-
LOGGER.warn("No handler found for /{} command", command);
43-
return;
44-
}
45-
46-
Class<?> handler =
47-
(Class<?>) commandClassMethodInstance.getClass().getMethod("clazz")
48-
.invoke(commandClassMethodInstance);
39+
ReflectiveSlashCommandClassMethod reflectiveSlashCommandClassMethod =
40+
ReflectiveLoader.proxy(
41+
reflectiveSlashCommandLoader.getSlashCommandClassMethod(command),
42+
ReflectiveSlashCommandClassMethod.class
43+
);
4944

50-
Method method =
51-
(Method) commandClassMethodInstance.getClass().getMethod("method")
52-
.invoke(commandClassMethodInstance);
45+
Class<?> handler = reflectiveSlashCommandClassMethod.clazz();
46+
Method method = reflectiveSlashCommandClassMethod.method();
5347

5448
List<Object> paramOrder = new ArrayList<>();
5549
Parameter[] parameters = method.getParameters();
50+
5651
for (Parameter parameter : parameters) {
5752
if (parameter.getParameterizedType() == interaction.getClass()) {
5853
paramOrder.add(interaction);
@@ -88,13 +83,10 @@ public void onInteractionCreate(Interaction interaction, Guild guild) {
8883
}
8984
}
9085

91-
private void injectComponents(Object object) throws Exception {
92-
Class<?> slashCommandLoaderClass = slashCommandLoader.getClass();
86+
private void injectComponents(Object object) {
87+
ReflectiveSlashCommandLoader reflectiveSlashCommandLoader =
88+
ReflectiveLoader.proxy(slashCommandLoader, ReflectiveSlashCommandLoader.class);
9389

94-
Method injectComponentsMethod =
95-
slashCommandLoaderClass.getMethod("injectComponents", Object.class);
96-
97-
injectComponentsMethod.invoke(slashCommandLoader, object);
90+
reflectiveSlashCommandLoader.injectComponents(object);
9891
}
99-
10092
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.javadiscord.jdi.internal;
2+
3+
public interface ReflectiveComponentLoader {
4+
void loadComponents();
5+
6+
void injectComponents(Object component);
7+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.javadiscord.jdi.internal;
2+
3+
import java.lang.reflect.InvocationHandler;
4+
import java.lang.reflect.Proxy;
5+
6+
public class ReflectiveLoader {
7+
public static <T> T proxy(Object object, Class<T> interfaceClass) {
8+
InvocationHandler handler =
9+
(proxy, method, methodArgs) -> object.getClass()
10+
.getMethod(method.getName(), method.getParameterTypes())
11+
.invoke(object, methodArgs);
12+
13+
@SuppressWarnings("unchecked") T proxyInstance =
14+
(T) Proxy.newProxyInstance(
15+
interfaceClass.getClassLoader(),
16+
new Class[] {interfaceClass},
17+
handler
18+
);
19+
20+
return proxyInstance;
21+
}
22+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.javadiscord.jdi.internal;
2+
3+
import java.lang.reflect.Method;
4+
5+
public interface ReflectiveSlashCommandClassMethod {
6+
Class<?> clazz();
7+
8+
Method method();
9+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.javadiscord.jdi.internal;
2+
3+
public interface ReflectiveSlashCommandLoader {
4+
Object getSlashCommandClassMethod(String name);
5+
6+
void injectComponents(Object object);
7+
}

settings.gradle

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,3 @@ include 'example:echo-bot'
99
findProject(':example:echo-bot')?.name = 'echo-bot'
1010
include 'example:lj-discord-bot'
1111
findProject(':example:lj-discord-bot')?.name = 'lj-discord-bot'
12-

0 commit comments

Comments
 (0)