package noppes.npcs.controllers; import cpw.mods.fml.common.eventhandler.Cancelable; import noppes.npcs.api.handler.IHookDefinition; import noppes.npcs.janino.annotations.ParamName; import java.lang.reflect.Method; import java.lang.reflect.Parameter; /** * Implementation of {@link IHookDefinition} with a fluent builder API. * *

Usage Examples

*
{@code
 * // Recommended: auto-derives imports from event class
 * HookDefinition.of("onDBCTransform", IDBCEvent.TransformEvent.class);
 *
 * // Full control with builder
 * HookDefinition.builder("onDBCPowerUp")
 *     .eventClass(IDBCEvent.PowerUpEvent.class)
 *     .cancelable(true)
 *     .build();
 *
 * // Simple hook (no event metadata)
 * HookDefinition.simple("customHook");
 * }
*/ public class HookDefinition implements IHookDefinition { private final String hookName; private final String eventClassName; private final String[] paramNames; private final String[] requiredImports; private final boolean cancelable; private transient Class cachedEventClass; private transient boolean classResolutionAttempted; private HookDefinition(Builder builder) { this.hookName = builder.hookName; this.eventClassName = builder.eventClassName; this.paramNames = builder.paramNames != null ? builder.paramNames : new String[]{"event"}; this.requiredImports = builder.requiredImports != null ? builder.requiredImports : new String[0]; this.cancelable = builder.cancelable; } // ==================== IHookDefinition Implementation ==================== @Override public String hookName() { return hookName; } @Override public String eventClassName() { return eventClassName; } @Override public String[] paramNames() { return paramNames; } @Override public String[] requiredImports() { return requiredImports; } @Override public boolean isCancelable() { return cancelable; } @Override public Class getEventClass() { if (classResolutionAttempted) { return cachedEventClass; } classResolutionAttempted = true; if (eventClassName == null || eventClassName.isEmpty()) { return null; } try { cachedEventClass = Class.forName(eventClassName); } catch (ClassNotFoundException e) { cachedEventClass = null; } return cachedEventClass; } // ==================== Factory Methods ==================== /** * Create a hook definition from a hook name and event class. * Automatically derives imports from the event class. * * @param hookName The hook function name * @param eventClass The event class (e.g., INpcEvent.InitEvent.class) * @return A hook definition with auto-derived imports */ public static HookDefinition of(String hookName, Class eventClass) { return builder(hookName).eventClass(eventClass).build(); } /** * Create a simple hook definition with just a name (no event metadata). * * @param hookName The hook function name * @return A minimal hook definition */ public static HookDefinition simple(String hookName) { return builder(hookName).build(); } /** * Create a new builder for a hook definition. * * @param hookName The hook function name * @return A new builder instance */ public static Builder builder(String hookName) { return new Builder(hookName); } /** * Create a hook definition from an interface method. * Automatically extracts metadata from the method signature and @ParamName annotations. * This provides backward compatibility with the annotation-based hook definition approach. * * @param hookName The hook name (may differ from method name) * @param method The method to extract metadata from * @return A hook definition with extracted metadata */ public static HookDefinition fromMethod(String hookName, Method method) { Builder builder = builder(hookName); // Extract event type from first parameter if (method.getParameterCount() > 0) { Class eventType = method.getParameterTypes()[0]; builder.eventClass(eventType); // Auto-detect cancelable from event class if (eventType.isAnnotationPresent(Cancelable.class)) { builder.cancelable(true); } // Build required imports from event type String importName = getImportForClass(eventType); if (importName != null) { builder.requiredImports(importName); } } // Extract parameter names from @ParamName annotations Parameter[] params = method.getParameters(); if (params.length > 0) { String[] names = new String[params.length]; for (int i = 0; i < params.length; i++) { ParamName annotation = params[i].getAnnotation(ParamName.class); if (annotation != null) { names[i] = annotation.value(); } else { // Fallback: use lowercase simple type name names[i] = Character.toLowerCase(params[i].getType().getSimpleName().charAt(0)) + params[i].getType().getSimpleName().substring(1); } } builder.paramNames(names); } return builder.build(); } /** * Create a hook definition from a method using its name as the hook name. * * @param method The method to extract metadata from * @return A hook definition with extracted metadata */ public static HookDefinition fromMethod(Method method) { return fromMethod(method.getName(), method); } /** * Get the import statement needed for a class. * For nested classes, returns the enclosing class. */ private static String getImportForClass(Class clazz) { if (clazz == null || clazz.isPrimitive()) { return null; } // For nested classes, get the top-level enclosing class Class enclosing = clazz; while (enclosing.getEnclosingClass() != null) { enclosing = enclosing.getEnclosingClass(); } String name = enclosing.getName(); if (name.startsWith("java.lang.")) { return null; } return name; } // ==================== Builder ==================== public static class Builder { private final String hookName; private String eventClassName; private String[] paramNames; private String[] requiredImports; private boolean cancelable; private Builder(String hookName) { if (hookName == null || hookName.isEmpty()) { throw new IllegalArgumentException("hookName cannot be null or empty"); } this.hookName = hookName; } /** * Set the event class directly. * Automatically derives: class name, required imports, and cancelable status. */ public Builder eventClass(Class clazz) { if (clazz != null) { this.eventClassName = clazz.getName(); String importName = getImportForClass(clazz); if (importName != null) { this.requiredImports = new String[]{importName}; } if (clazz.isAnnotationPresent(Cancelable.class)) { this.cancelable = true; } } return this; } /** * Set the event class by name (for addon classes that may not be loaded). */ public Builder eventClass(String className) { this.eventClassName = className; return this; } /** * Set parameter names for stub generation. */ public Builder paramNames(String... names) { this.paramNames = names; return this; } /** * Set required imports (only needed if using eventClass(String)). */ public Builder requiredImports(String... imports) { this.requiredImports = imports; return this; } /** * Set whether the event is cancelable. */ public Builder cancelable(boolean cancelable) { this.cancelable = cancelable; return this; } public HookDefinition build() { return new HookDefinition(this); } } // ==================== Object Methods ==================== @Override public String toString() { return "HookDefinition{hookName='" + hookName + "', eventClass='" + eventClassName + "'}"; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof HookDefinition)) return false; return hookName.equals(((HookDefinition) obj).hookName); } @Override public int hashCode() { return hookName.hashCode(); } }