Compare commits

..

No commits in common. "d8504a7cdef9b2798dc5d7e44293469584f650af" and "0ea330d48b33e72f2df928f6de685d1559a56642" have entirely different histories.

23 changed files with 91 additions and 572 deletions

@ -21,13 +21,13 @@ dependencies {
testRuntimeOnly("org.junit.platform:junit-platform-launcher") testRuntimeOnly("org.junit.platform:junit-platform-launcher")
implementation(libs.log4j) implementation(libs.log4j)
implementation(libs.geantyref)
} }
// Apply a specific Java toolchain to ease working on different environments. // Apply a specific Java toolchain to ease working on different environments.
java { java {
toolchain { toolchain {
languageVersion = JavaLanguageVersion.of(23) languageVersion = JavaLanguageVersion.of(21)
} }
} }

@ -1,34 +1,22 @@
package net.tomatentum.marinara; package net.tomatentum.marinara;
import net.tomatentum.marinara.registry.InteractionCheckRegistry;
import net.tomatentum.marinara.registry.InteractionRegistry; import net.tomatentum.marinara.registry.InteractionRegistry;
import net.tomatentum.marinara.wrapper.LibraryWrapper; import net.tomatentum.marinara.wrapper.LibraryWrapper;
public class Marinara { public class Marinara {
public static <T extends LibraryWrapper> Marinara load(LibraryWrapper wrapper) { public static <T extends LibraryWrapper> Marinara load(LibraryWrapper wrapper) {
return new Marinara(wrapper); InteractionRegistry registry = new InteractionRegistry(wrapper);
return new Marinara(registry);
} }
private InteractionRegistry registry; private InteractionRegistry registry;
private InteractionCheckRegistry checkRegistry;
private LibraryWrapper wrapper;
private Marinara(LibraryWrapper wrapper) { private Marinara(InteractionRegistry registry) {
this.wrapper = wrapper; this.registry = registry;
this.registry = new InteractionRegistry(this);
this.checkRegistry = new InteractionCheckRegistry();
} }
public InteractionRegistry getRegistry() { public InteractionRegistry getRegistry() {
return registry; return registry;
} }
public InteractionCheckRegistry getCheckRegistry() {
return checkRegistry;
}
public LibraryWrapper getWrapper() {
return wrapper;
}
} }

@ -1,41 +0,0 @@
package net.tomatentum.marinara.checks;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import net.tomatentum.marinara.util.ReflectionUtil;
public record AppliedCheck(InteractionCheck<?> check, Annotation annotation) {
public boolean pre(Object context) {
Method[] methods = Arrays.stream(check.getClass().getMethods())
.filter(x -> x.getName().equals("preExec"))
.filter(x -> !x.isBridge())
.toArray(s -> new Method[s]);
Method method = ReflectionUtil.getMostSpecificMethod(methods, context.getClass(), annotation.annotationType());
method.setAccessible(true);
try {
return (boolean) method.invoke(check, context, annotation);
} catch (IllegalAccessException | InvocationTargetException | SecurityException e) {
e.printStackTrace();
return false;
}
}
public void post(Object context) {
Method[] methods = Arrays.stream(check.getClass().getMethods())
.filter(x -> x.getName().equals("postExec"))
.filter(x -> !x.isBridge())
.toArray(s -> new Method[s]);
Method method = ReflectionUtil.getMostSpecificMethod(methods, context.getClass(), annotation.annotationType());
method.setAccessible(true);
try {
method.invoke(check, context, annotation);
} catch (IllegalAccessException | InvocationTargetException | SecurityException e) {
e.printStackTrace();
}
}
}

@ -1,10 +0,0 @@
package net.tomatentum.marinara.checks;
import java.lang.annotation.Annotation;
public interface InteractionCheck<A extends Annotation> {
public boolean preExec(Object context, A annotation);
public void postExec(Object context, A annotation);
}

@ -16,18 +16,9 @@ public record ExecutableSlashCommandDefinition(
if (!(o instanceof ExecutableSlashCommandDefinition)) if (!(o instanceof ExecutableSlashCommandDefinition))
return false; return false;
ExecutableSlashCommandDefinition other = (ExecutableSlashCommandDefinition) o; ExecutableSlashCommandDefinition other = (ExecutableSlashCommandDefinition) o;
boolean equals = false; return other.applicationCommand.name().equals(this.applicationCommand.name()) &&
other.subCommandGroup.name().equals(this.subCommandGroup.name()) &&
if (this.applicationCommand() != null && other.subCommandGroup() != null) other.subCommand.name().equals(this.subCommand.name());
equals = this.applicationCommand.name().equals(other.applicationCommand().name());
if (this.subCommandGroup() != null && other.subCommandGroup() != null)
equals = this.subCommandGroup().name().equals(other.subCommandGroup().name());
if (this.subCommand() != null && other.subCommand() != null)
equals = this.subCommand().name().equals(other.subCommand().name());
return equals;
} }
@Override @Override

@ -2,40 +2,39 @@ package net.tomatentum.marinara.interaction.methods;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import net.tomatentum.marinara.Marinara;
import net.tomatentum.marinara.interaction.InteractionHandler; import net.tomatentum.marinara.interaction.InteractionHandler;
import net.tomatentum.marinara.interaction.InteractionType; import net.tomatentum.marinara.interaction.InteractionType;
import net.tomatentum.marinara.parser.AnnotationParser; import net.tomatentum.marinara.interaction.annotation.Button;
import net.tomatentum.marinara.parser.ButtonParser; import net.tomatentum.marinara.wrapper.LibraryWrapper;
public class ButtonInteractionMethod extends InteractionMethod { public class ButtonInteractionMethod extends InteractionMethod {
private String customId; private String customId;
ButtonInteractionMethod(Method method, InteractionHandler handler, Marinara marinara) { ButtonInteractionMethod(Method method, InteractionHandler handler, LibraryWrapper wrapper) {
super(method, handler, marinara); super(method, handler, wrapper);
} parseMethod();
@Override
public AnnotationParser[] getParsers() {
return new AnnotationParser[] {
new ButtonParser(method, (x) -> { this.customId = x; } )
};
} }
@Override @Override
public Object getParameter(Object parameter, int index) { public Object getParameter(Object parameter, int index) {
Class<?> type = getMethod().getParameterTypes()[index+1]; Class<?> type = getMethod().getParameterTypes()[index+1];
return marinara.getWrapper().getComponentContextObject(parameter, type); return wrapper.getComponentContextObject(parameter, type);
} }
@Override @Override
public boolean canRun(Object context) { public boolean canRun(Object context) {
return marinara.getWrapper().getButtonId(context).equals(customId); return wrapper.getButtonId(context).equals(customId);
} }
@Override @Override
public InteractionType getType() { public InteractionType getType() {
return InteractionType.BUTTON; return InteractionType.BUTTON;
} }
private void parseMethod() {
Button button = getMethod().getAnnotation(Button.class);
this.customId = button.value();
}
} }

@ -7,52 +7,35 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import net.tomatentum.marinara.Marinara;
import net.tomatentum.marinara.checks.AppliedCheck;
import net.tomatentum.marinara.interaction.InteractionHandler; import net.tomatentum.marinara.interaction.InteractionHandler;
import net.tomatentum.marinara.interaction.InteractionType; import net.tomatentum.marinara.interaction.InteractionType;
import net.tomatentum.marinara.interaction.annotation.Button; import net.tomatentum.marinara.interaction.annotation.Button;
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand; import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand;
import net.tomatentum.marinara.interaction.commands.annotation.SubCommand; import net.tomatentum.marinara.interaction.commands.annotation.SubCommand;
import net.tomatentum.marinara.parser.AnnotationParser; import net.tomatentum.marinara.wrapper.LibraryWrapper;
import net.tomatentum.marinara.parser.InteractionCheckParser;
public abstract class InteractionMethod { public abstract class InteractionMethod {
public static InteractionMethod create(Method method, InteractionHandler handler, Marinara marinara) { public static InteractionMethod create(Method method, InteractionHandler handler, LibraryWrapper wrapper) {
if (method.isAnnotationPresent(SlashCommand.class) || method.isAnnotationPresent(SubCommand.class)) if (method.isAnnotationPresent(SlashCommand.class) || method.isAnnotationPresent(SubCommand.class))
return new SlashCommandInteractionMethod(method, handler, marinara); return new SlashCommandInteractionMethod(method, handler, wrapper);
if (method.isAnnotationPresent(Button.class)) if (method.isAnnotationPresent(Button.class))
return new ButtonInteractionMethod(method, handler, marinara); return new ButtonInteractionMethod(method, handler, wrapper);
return null; return null;
} }
protected Method method; protected Method method;
protected InteractionHandler handler; protected InteractionHandler handler;
protected Marinara marinara; protected LibraryWrapper wrapper;
protected List<AnnotationParser> parsers;
protected List<AppliedCheck> appliedChecks;
protected InteractionMethod(Method method, protected InteractionMethod(Method method, InteractionHandler handler, LibraryWrapper wrapper) {
InteractionHandler handler,
Marinara marinara
) {
if (!Arrays.asList(handler.getClass().getMethods()).contains(method)) if (!Arrays.asList(handler.getClass().getMethods()).contains(method))
throw new InvalidParameterException("Method does not apply to specified handler"); throw new InvalidParameterException("Method does not apply to specified handler");
this.method = method; this.method = method;
this.handler = handler; this.handler = handler;
this.marinara = marinara; this.wrapper = wrapper;
this.parsers = new ArrayList<>(Arrays.asList(getParsers()));
this.appliedChecks = new ArrayList<>();
parsers.add(new InteractionCheckParser(method, appliedChecks::add, marinara.getCheckRegistry()));
parsers.stream().forEach(AnnotationParser::parse);
} }
public abstract AnnotationParser[] getParsers();
public abstract Object getParameter(Object parameter, int index); public abstract Object getParameter(Object parameter, int index);
public abstract boolean canRun(Object context); public abstract boolean canRun(Object context);
@ -60,24 +43,6 @@ public abstract class InteractionMethod {
public abstract InteractionType getType(); public abstract InteractionType getType();
public void run(Object context) { public void run(Object context) {
if (this.appliedChecks.stream().filter(x -> !x.pre(context)).count() > 0)
return;
method.setAccessible(true);
try {
method.invoke(handler, getParameters(context));
}catch (IllegalAccessException | InvocationTargetException ex) {
throw new RuntimeException(ex);
}
this.appliedChecks.forEach(x -> x.post(context));
}
public Method getMethod() {
return method;
}
private Object[] getParameters(Object context) {
int parameterCount = method.getParameterCount(); int parameterCount = method.getParameterCount();
List<Object> parameters = new ArrayList<>(); List<Object> parameters = new ArrayList<>();
@ -88,7 +53,16 @@ public abstract class InteractionMethod {
} }
parameters.add(getParameter(context, i-1)); parameters.add(getParameter(context, i-1));
} }
return parameters.toArray(); method.setAccessible(true);
try {
method.invoke(handler, parameters.toArray());
}catch (IllegalAccessException | InvocationTargetException ex) {
throw new RuntimeException(ex);
}
}
public Method getMethod() {
return method;
} }
} }

@ -2,36 +2,32 @@ package net.tomatentum.marinara.interaction.methods;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import net.tomatentum.marinara.Marinara;
import net.tomatentum.marinara.interaction.InteractionHandler; import net.tomatentum.marinara.interaction.InteractionHandler;
import net.tomatentum.marinara.interaction.InteractionType; import net.tomatentum.marinara.interaction.InteractionType;
import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition; import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition;
import net.tomatentum.marinara.parser.AnnotationParser; import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand;
import net.tomatentum.marinara.parser.SlashCommandParser; import net.tomatentum.marinara.interaction.commands.annotation.SubCommand;
import net.tomatentum.marinara.interaction.commands.annotation.SubCommandGroup;
import net.tomatentum.marinara.util.ReflectionUtil;
import net.tomatentum.marinara.wrapper.LibraryWrapper;
public class SlashCommandInteractionMethod extends InteractionMethod { public class SlashCommandInteractionMethod extends InteractionMethod {
private ExecutableSlashCommandDefinition commandDefinition; private ExecutableSlashCommandDefinition commandDefinition;
SlashCommandInteractionMethod(Method method, InteractionHandler handler, Marinara marinara) { SlashCommandInteractionMethod(Method method, InteractionHandler handler, LibraryWrapper wrapper) {
super(method, handler, marinara); super(method, handler, wrapper);
} parseMethod();
@Override
public AnnotationParser[] getParsers() {
return new AnnotationParser[] {
new SlashCommandParser(method, (x) -> { this.commandDefinition = x; } )
};
} }
@Override @Override
public Object getParameter(Object context, int index) { public Object getParameter(Object context, int index) {
return marinara.getWrapper().convertCommandOption(context, commandDefinition.options()[index].type(), commandDefinition.options()[index].name()); return wrapper.convertCommandOption(context, commandDefinition.options()[index].type(), commandDefinition.options()[index].name());
} }
@Override @Override
public boolean canRun(Object context) { public boolean canRun(Object context) {
ExecutableSlashCommandDefinition other = marinara.getWrapper().getCommandDefinition(context); ExecutableSlashCommandDefinition other = wrapper.getCommandDefinition(context);
return commandDefinition.equals(other); return commandDefinition.equals(other);
} }
@ -44,8 +40,24 @@ public class SlashCommandInteractionMethod extends InteractionMethod {
return commandDefinition; return commandDefinition;
} }
public void setCommandDefinition(ExecutableSlashCommandDefinition commandDefinition) { private void parseMethod() {
this.commandDefinition = commandDefinition; ReflectionUtil.checkValidCommandMethod(method);
SlashCommand cmd = ReflectionUtil.getAnnotation(method, SlashCommand.class);
ExecutableSlashCommandDefinition.Builder builder = new ExecutableSlashCommandDefinition.Builder();
builder.setApplicationCommand(cmd);
if (ReflectionUtil.isAnnotationPresent(method, SubCommandGroup.class)) {
SubCommandGroup cmdGroup = ReflectionUtil.getAnnotation(method, SubCommandGroup.class);
builder.setSubCommandGroup(cmdGroup);
}
if (ReflectionUtil.isAnnotationPresent(method, SubCommand.class)) {
SubCommand subCmd = ReflectionUtil.getAnnotation(method, SubCommand.class);
builder.setSubCommand(subCmd);
}
this.commandDefinition = builder.build();
} }
} }

@ -1,8 +0,0 @@
package net.tomatentum.marinara.parser;
import java.lang.reflect.Method;
public interface AnnotationParser {
void parse();
Method getMethod();
}

@ -1,29 +0,0 @@
package net.tomatentum.marinara.parser;
import java.lang.reflect.Method;
import java.util.function.Consumer;
import net.tomatentum.marinara.interaction.annotation.Button;
public class ButtonParser implements AnnotationParser {
private Method method;
private Consumer<String> consumer;
public ButtonParser(Method method, Consumer<String> consumer) {
this.method = method;
this.consumer = consumer;
}
@Override
public void parse() {
Button button = getMethod().getAnnotation(Button.class);
this.consumer.accept(button.value());
}
@Override
public Method getMethod() {
return this.method;
}
}

@ -1,42 +0,0 @@
package net.tomatentum.marinara.parser;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.Consumer;
import net.tomatentum.marinara.checks.AppliedCheck;
import net.tomatentum.marinara.checks.InteractionCheck;
import net.tomatentum.marinara.registry.InteractionCheckRegistry;
public class InteractionCheckParser implements AnnotationParser {
private InteractionCheckRegistry checkRegistry;
private Method method;
private Consumer<AppliedCheck> consumer;
public InteractionCheckParser(Method method, Consumer<AppliedCheck> consumer, InteractionCheckRegistry checkRegistry) {
this.checkRegistry = checkRegistry;
this.method = method;
this.consumer = consumer;
}
@Override
public void parse() {
Annotation[] annotations = method.getAnnotations();
Arrays.stream(annotations).forEach(this::convertAnnotation);
}
private void convertAnnotation(Annotation annotation) {
Optional<InteractionCheck<?>> check = this.checkRegistry.getCheckFromAnnotation(annotation.annotationType());
if (check.isPresent())
consumer.accept(new AppliedCheck(check.get(), annotation));
}
@Override
public Method getMethod() {
return this.method;
}
}

@ -1,63 +0,0 @@
package net.tomatentum.marinara.parser;
import java.lang.reflect.Method;
import java.util.function.Consumer;
import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition;
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand;
import net.tomatentum.marinara.interaction.commands.annotation.SubCommand;
import net.tomatentum.marinara.interaction.commands.annotation.SubCommandGroup;
import net.tomatentum.marinara.util.ReflectionUtil;
public class SlashCommandParser implements AnnotationParser {
private Method method;
private Consumer<ExecutableSlashCommandDefinition> consumer;
public SlashCommandParser(Method method, Consumer<ExecutableSlashCommandDefinition> consumer) {
this.method = method;
this.consumer = consumer;
}
@Override
public void parse() {
this.checkValidCommandMethod(method);
SlashCommand cmd = ReflectionUtil.getAnnotation(method, SlashCommand.class);
ExecutableSlashCommandDefinition.Builder builder = new ExecutableSlashCommandDefinition.Builder();
builder.setApplicationCommand(cmd);
if (ReflectionUtil.isAnnotationPresent(method, SubCommandGroup.class)) {
SubCommandGroup cmdGroup = ReflectionUtil.getAnnotation(method, SubCommandGroup.class);
builder.setSubCommandGroup(cmdGroup);
}
if (ReflectionUtil.isAnnotationPresent(method, SubCommand.class)) {
SubCommand subCmd = ReflectionUtil.getAnnotation(method, SubCommand.class);
builder.setSubCommand(subCmd);
}
consumer.accept(builder.build());
}
@Override
public Method getMethod() {
return this.method;
}
private void checkValidCommandMethod(Method method) {
if (method.isAnnotationPresent(SlashCommand.class) &&
method.getDeclaringClass().isAnnotationPresent(SlashCommand.class)) {
throw new RuntimeException(method.getName() + ": Can't have ApplicationCommand Annotation on Class and Method");
}
if (!ReflectionUtil.isAnnotationPresent(method, SlashCommand.class))
throw new RuntimeException(method.getName() + ": Missing ApplicationCommand Annotation on either Class or Method");
if ((method.isAnnotationPresent(SubCommand.class) &&
!ReflectionUtil.isAnnotationPresent(method, SlashCommand.class))) {
throw new RuntimeException(method.getName() + ": Missing ApplicationCommand Annotation on either Method or Class");
}
}
}

@ -1,34 +0,0 @@
package net.tomatentum.marinara.registry;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import io.leangen.geantyref.GenericTypeReflector;
import net.tomatentum.marinara.checks.InteractionCheck;
public class InteractionCheckRegistry {
private List<InteractionCheck<?>> checks;
public InteractionCheckRegistry() {
this.checks = new ArrayList<>();
}
public void addCheck(InteractionCheck<?> check) {
checks.add(check);
}
public Optional<InteractionCheck<?>> getCheckFromAnnotation(Type annotation) {
for (InteractionCheck<?> interactionCheck : checks) {
ParameterizedType type = (ParameterizedType) GenericTypeReflector.getExactSuperType(interactionCheck.getClass(), InteractionCheck.class);
Type typeParam = type.getActualTypeArguments()[0];
if (typeParam.equals(annotation))
return Optional.of(interactionCheck);
}
return Optional.empty();
}
}

@ -5,27 +5,27 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import net.tomatentum.marinara.Marinara;
import net.tomatentum.marinara.interaction.InteractionHandler; import net.tomatentum.marinara.interaction.InteractionHandler;
import net.tomatentum.marinara.interaction.InteractionType; import net.tomatentum.marinara.interaction.InteractionType;
import net.tomatentum.marinara.interaction.commands.SlashCommandDefinition; import net.tomatentum.marinara.interaction.commands.SlashCommandDefinition;
import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition; import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition;
import net.tomatentum.marinara.interaction.methods.SlashCommandInteractionMethod; import net.tomatentum.marinara.interaction.methods.SlashCommandInteractionMethod;
import net.tomatentum.marinara.interaction.methods.InteractionMethod; import net.tomatentum.marinara.interaction.methods.InteractionMethod;
import net.tomatentum.marinara.wrapper.LibraryWrapper;
public class InteractionRegistry { public class InteractionRegistry {
private List<InteractionMethod> interactionMethods; private List<InteractionMethod> interactionMethods;
private Marinara marinara; private LibraryWrapper wrapper;
public InteractionRegistry(Marinara marinara) { public InteractionRegistry(LibraryWrapper wrapper) {
this.interactionMethods = new ArrayList<>(); this.interactionMethods = new ArrayList<>();
this.marinara = marinara; this.wrapper = wrapper;
marinara.getWrapper().subscribeInteractions(this::handle); wrapper.subscribeInteractions(this::handle);
} }
public void addInteractions(InteractionHandler interactionHandler) { public void addInteractions(InteractionHandler interactionHandler) {
for (Method method : interactionHandler.getClass().getMethods()) { for (Method method : interactionHandler.getClass().getMethods()) {
InteractionMethod iMethod = InteractionMethod.create(method, interactionHandler, marinara); InteractionMethod iMethod = InteractionMethod.create(method, interactionHandler, wrapper);
if (iMethod != null) if (iMethod != null)
this.interactionMethods.add(iMethod); this.interactionMethods.add(iMethod);
} }
@ -48,13 +48,13 @@ public class InteractionRegistry {
defs.add(new SlashCommandDefinition(def.applicationCommand()).addExecutableCommand(def)); defs.add(new SlashCommandDefinition(def.applicationCommand()).addExecutableCommand(def));
}); });
marinara.getWrapper().registerSlashCommands(defs.toArray(new SlashCommandDefinition[0])); wrapper.registerSlashCommands(defs.toArray(new SlashCommandDefinition[0]));
} }
public void handle(Object context) { public void handle(Object context) {
interactionMethods.forEach((m) -> { interactionMethods.forEach((m) -> {
InteractionType type = marinara.getWrapper().getInteractionType(context.getClass()); InteractionType type = wrapper.getInteractionType(context.getClass());
if (m.getType().equals(type) && m.canRun(context)) if (m.getType().equals(type))
m.run(context); m.run(context);
}); });
} }

@ -2,10 +2,9 @@ package net.tomatentum.marinara.util;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays; import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand;
import java.util.List; import net.tomatentum.marinara.interaction.commands.annotation.SubCommand;
import java.util.Objects;
public final class ReflectionUtil { public final class ReflectionUtil {
@ -22,82 +21,19 @@ public final class ReflectionUtil {
method.getDeclaringClass().getAnnotation(annotationClass); method.getDeclaringClass().getAnnotation(annotationClass);
} }
public static int getCastDepth(Class<?> child, Class<?> parent) { public static void checkValidCommandMethod(Method method) {
if (method.isAnnotationPresent(SlashCommand.class) &&
if (parent.equals(Object.class)) method.getDeclaringClass().isAnnotationPresent(SlashCommand.class)) {
return Integer.MAX_VALUE; throw new RuntimeException(method.getName() + ": Can't have ApplicationCommand Annotation on Class and Method");
if (!parent.isAssignableFrom(child)) {
throw new IllegalArgumentException("The specified class is not a child class of the specified parent.");
} }
int depth = 0; if (!isAnnotationPresent(method, SlashCommand.class))
Class<?> curr = child; throw new RuntimeException(method.getName() + ": Missing ApplicationCommand Annotation on either Class or Method");
List<Class<?>> parents = new ArrayList<>();
while (!curr.equals(parent)) { if ((method.isAnnotationPresent(SubCommand.class) &&
depth++; !isAnnotationPresent(method, SlashCommand.class))) {
parents.add(curr.getSuperclass()); throw new RuntimeException(method.getName() + ": Missing ApplicationCommand Annotation on either Method or Class");
parents.addAll(Arrays.asList(curr.getInterfaces()));
for (Class<?> currParent : parents) {
if (currParent != null && parent.isAssignableFrom(currParent)) {
curr = currParent;
break;
} }
} }
parents.clear();
}
return depth;
}
public static Method getMostSpecificMethod(Method[] methods, Class<?>... parameters) {
List<Method> compatibleMethods = Arrays.stream(methods)
.filter(x -> isMethodCallable(x, parameters))
.toList();
if (compatibleMethods.size() == 0)
throw new IllegalArgumentException("There are no compatible Methods provided");
for (int i = 0; i < parameters.length; i++) {
final int currI = i;
Class<?>[] parameterTypes = compatibleMethods.stream()
.map(x -> x.getParameterTypes()[currI])
.toArray(x -> new Class[x]);
Class<?> mostSpecific = getMostSpecificClass(parameterTypes, parameters[i]);
compatibleMethods = compatibleMethods.stream()
.filter(x -> Objects.equals(x.getParameterTypes()[currI], mostSpecific))
.toList();
}
return compatibleMethods.getFirst();
}
public static Class<?> getMostSpecificClass(Class<?>[] classes, Class<?> base) {
int min = Integer.MAX_VALUE;
Class<?> currMostSpecific = null;
for (Class<?> currClass : classes) {
int currCastDepth = getCastDepth(base, currClass);
if (currCastDepth <= min) {
min = currCastDepth;
currMostSpecific = currClass;
}
}
return currMostSpecific;
}
public static boolean isMethodCallable(Method method, Class<?>... parameters) {
if (!Objects.equals(method.getParameterCount(), parameters.length))
return false;
Class<?>[] methodParams = method.getParameterTypes();
for (int i = 0; i < parameters.length; i++) {
if (!methodParams[i].isAssignableFrom(parameters[i]))
return false;
}
return true;
}
} }

@ -29,7 +29,7 @@ dependencies {
// Apply a specific Java toolchain to ease working on different environments. // Apply a specific Java toolchain to ease working on different environments.
java { java {
toolchain { toolchain {
languageVersion = JavaLanguageVersion.of(23) languageVersion = JavaLanguageVersion.of(21)
} }
} }

@ -1,41 +0,0 @@
package net.tomatentum.marinara.wrapper.javacord.checks;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Optional;
import org.javacord.api.entity.permission.PermissionType;
import org.javacord.api.entity.server.Server;
import org.javacord.api.interaction.InteractionBase;
import net.tomatentum.marinara.checks.InteractionCheck;
public class PermissionCheck implements InteractionCheck<PermissionCheck.HasPermission> {
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public static @interface HasPermission {
public PermissionType[] value();
}
@Override
public boolean preExec(Object context, HasPermission annotation) {
throw new UnsupportedOperationException("Unimplemented method 'preExec'");
}
public boolean preExec(InteractionBase context, HasPermission annotation) {
Optional<Server> server = context.getServer();
if (!server.isPresent())
return false;
return server.get().hasPermissions(context.getUser(), annotation.value());
}
@Override
public void postExec(Object context, HasPermission annotation) {
}
}

@ -20,7 +20,7 @@ public class ButtonTest {
LibraryWrapper wrapper = new JavacordWrapper(new DiscordApiMock()); //null okay as we don't use the discord API in this test. LibraryWrapper wrapper = new JavacordWrapper(new DiscordApiMock()); //null okay as we don't use the discord API in this test.
Marinara marinara = Marinara.load(wrapper); Marinara marinara = Marinara.load(wrapper);
marinara.getRegistry().addInteractions(new TestButton()); marinara.getRegistry().addInteractions(new TestButton());
wrapper.handleInteraction(new ButtonInteractionMock("test")); wrapper.handleInteraction(new ButtonInteractionMock());
assertTrue(TestButton.didRun); assertTrue(TestButton.didRun);
} }

@ -1,47 +0,0 @@
package net.tomatentum.marinara.test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.javacord.api.entity.permission.PermissionType;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import net.tomatentum.marinara.Marinara;
import net.tomatentum.marinara.test.mocks.ButtonInteractionMock;
import net.tomatentum.marinara.test.mocks.DiscordApiMock;
import net.tomatentum.marinara.test.mocks.ServerMock;
import net.tomatentum.marinara.wrapper.LibraryWrapper;
import net.tomatentum.marinara.wrapper.javacord.JavacordWrapper;
import net.tomatentum.marinara.wrapper.javacord.checks.PermissionCheck;
@TestInstance(Lifecycle.PER_CLASS)
public class InteractionCheckTest {
@Test
public void testInteractionCheck() {
LibraryWrapper wrapper = new JavacordWrapper(new DiscordApiMock());
Marinara marinara = Marinara.load(wrapper);
marinara.getCheckRegistry().addCheck(new TestInteractionCheck());
marinara.getRegistry().addInteractions(new TestButton());
wrapper.handleInteraction(new ButtonInteractionMock("test"));
assertTrue(TestInteractionCheck.preExecuted);
assertTrue(TestInteractionCheck.postExecuted);
}
@Test
public void testPermissionCheck() {
LibraryWrapper wrapper = new JavacordWrapper(new DiscordApiMock());
Marinara marinara = Marinara.load(wrapper);
marinara.getCheckRegistry().addCheck(new PermissionCheck());
marinara.getRegistry().addInteractions(new TestButton());
wrapper.handleInteraction(new ButtonInteractionMock("permissionCheck"));
assertTrue(TestButton.didPermRun);
TestButton.didPermRun = false;
ServerMock.TESTPERMISSION = PermissionType.ATTACH_FILE;
wrapper.handleInteraction(new ButtonInteractionMock("permissionCheck"));
assertFalse(TestButton.didPermRun);
}
}

@ -4,22 +4,18 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.javacord.api.entity.channel.TextChannel; import org.javacord.api.entity.channel.TextChannel;
import org.javacord.api.entity.message.Message; import org.javacord.api.entity.message.Message;
import org.javacord.api.entity.permission.PermissionType;
import org.javacord.api.entity.server.Server; import org.javacord.api.entity.server.Server;
import org.javacord.api.entity.user.User; import org.javacord.api.entity.user.User;
import org.javacord.api.interaction.ButtonInteraction; import org.javacord.api.interaction.ButtonInteraction;
import net.tomatentum.marinara.interaction.InteractionHandler; import net.tomatentum.marinara.interaction.InteractionHandler;
import net.tomatentum.marinara.interaction.annotation.Button; import net.tomatentum.marinara.interaction.annotation.Button;
import net.tomatentum.marinara.test.TestInteractionCheck.TestCheck;
import net.tomatentum.marinara.wrapper.javacord.checks.PermissionCheck.HasPermission;
public class TestButton implements InteractionHandler { public class TestButton implements InteractionHandler {
public static boolean didRun = false; public static boolean didRun = false;
@Button("test") @Button("test")
@TestCheck
public void exec(ButtonInteraction interaction, TextChannel channel, Message message, User member, Server server) { public void exec(ButtonInteraction interaction, TextChannel channel, Message message, User member, Server server) {
assertNotNull(interaction); assertNotNull(interaction);
assertNotNull(channel); assertNotNull(channel);
@ -30,13 +26,4 @@ public class TestButton implements InteractionHandler {
System.out.println("Success!"); System.out.println("Success!");
} }
public static boolean didPermRun = false;
@Button("permissionCheck")
@HasPermission({PermissionType.ADMINISTRATOR})
public void exec(ButtonInteraction interaction) {
didPermRun = true;
System.out.println("It worked!");
}
} }

@ -1,37 +0,0 @@
package net.tomatentum.marinara.test;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import net.tomatentum.marinara.checks.InteractionCheck;
public class TestInteractionCheck implements InteractionCheck<TestInteractionCheck.TestCheck> {
public static boolean preExecuted = false;
public static boolean postExecuted = false;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public static @interface TestCheck {
}
@Override
public boolean preExec(Object context, TestCheck annotation) {
assertNotNull(annotation);
assertNotNull(context);
preExecuted = true;
return true;
}
@Override
public void postExec(Object context, TestCheck annotation) {
assertNotNull(annotation);
assertNotNull(context);
postExecuted = true;
}
}

@ -23,12 +23,6 @@ import org.javacord.api.interaction.callback.InteractionOriginalResponseUpdater;
public class ButtonInteractionMock implements ButtonInteraction { public class ButtonInteractionMock implements ButtonInteraction {
private String customId;
public ButtonInteractionMock(String customId) {
this.customId = customId;
}
@Override @Override
public Message getMessage() { public Message getMessage() {
return new MessageMock(); return new MessageMock();
@ -36,7 +30,7 @@ public class ButtonInteractionMock implements ButtonInteraction {
@Override @Override
public String getCustomId() { public String getCustomId() {
return this.customId; return "test";
} }
@Override @Override

@ -1,7 +1,5 @@
package net.tomatentum.marinara.test.mocks; package net.tomatentum.marinara.test.mocks;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.time.Instant; import java.time.Instant;
import java.util.Collection; import java.util.Collection;
import java.util.EnumSet; import java.util.EnumSet;
@ -29,7 +27,6 @@ import org.javacord.api.entity.channel.ServerTextChannel;
import org.javacord.api.entity.channel.ServerThreadChannel; import org.javacord.api.entity.channel.ServerThreadChannel;
import org.javacord.api.entity.channel.ServerVoiceChannel; import org.javacord.api.entity.channel.ServerVoiceChannel;
import org.javacord.api.entity.emoji.KnownCustomEmoji; import org.javacord.api.entity.emoji.KnownCustomEmoji;
import org.javacord.api.entity.permission.PermissionType;
import org.javacord.api.entity.permission.Role; import org.javacord.api.entity.permission.Role;
import org.javacord.api.entity.server.ActiveThreads; import org.javacord.api.entity.server.ActiveThreads;
import org.javacord.api.entity.server.Ban; import org.javacord.api.entity.server.Ban;
@ -2262,12 +2259,5 @@ public class ServerMock implements Server {
// TODO Auto-generated method stub // TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getSystemChannelFlags'"); throw new UnsupportedOperationException("Unimplemented method 'getSystemChannelFlags'");
} }
public static PermissionType TESTPERMISSION = PermissionType.ADMINISTRATOR;
@Override
public boolean hasPermissions(User user, PermissionType... type) {
assertNotNull(user);
assertNotNull(type);
return TESTPERMISSION.equals(type[0]);
}
} }