package com.laytonsmith.core.events; import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery; import com.laytonsmith.PureUtilities.Common.StreamUtils; import com.laytonsmith.annotations.core; import com.laytonsmith.annotations.hide; import com.laytonsmith.core.Documentation; import com.laytonsmith.core.LogLevel; import com.laytonsmith.core.MethodScriptCompiler; import com.laytonsmith.core.ParseTree; import com.laytonsmith.core.Static; import com.laytonsmith.core.constructs.CArray; import com.laytonsmith.core.constructs.Construct; import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.environments.Environment; import com.laytonsmith.core.environments.GlobalEnv; import com.laytonsmith.core.exceptions.CRE.CREFormatException; import com.laytonsmith.core.exceptions.CancelCommandException; import com.laytonsmith.core.exceptions.ConfigRuntimeException; import com.laytonsmith.core.exceptions.EventException; import com.laytonsmith.core.exceptions.FunctionReturnException; import com.laytonsmith.core.exceptions.ProgramFlowManipulationException; import com.laytonsmith.core.profiler.ProfilePoint; import java.net.URL; import java.util.HashMap; import java.util.Map; /** * This helper class implements a few of the common functions in event, and * most (all?) Events should extend this class. * */ public abstract class AbstractEvent implements Event, Comparable<Event> { private EventMixinInterface mixin; // protected EventHandlerInterface handler; // // protected AbstractEvent(EventHandlerInterface handler){ // this.handler = handler; // } // public final void setAbstractEventMixin(EventMixinInterface mixin){ this.mixin = mixin; } /** * If the event needs to run special code when a player binds the event, it * can be done here. By default, an UnsupportedOperationException is thrown, * but is caught and ignored. */ @Override public void bind(BoundEvent event) { } /** * If the event needs to run special code when a player unbinds the event, it * can be done here. By default, an UnsupportedOperationException is thrown, * but is caught and ignored. */ @Override public void unbind(BoundEvent event) { } /** * If the event needs to run special code at server startup, it can be done * here. By default, nothing happens. */ @Override public void hook() { } /** * This function is run when the actual event occurs. * @param tree The compiled parse tree * @param b The bound event * @param env The operating environment * @param activeEvent The active event being executed */ @Override public final void execute(ParseTree tree, BoundEvent b, Environment env, BoundEvent.ActiveEvent activeEvent) throws ConfigRuntimeException{ try{ preExecution(env, activeEvent); } catch(UnsupportedOperationException e){ //Ignore. This particular event doesn't need to customize } ProfilePoint event = null; if(env.getEnv(GlobalEnv.class).GetProfiler() != null){ event = env.getEnv(GlobalEnv.class).GetProfiler().start("Event " + b.getEventName() + " (defined at " + b.getTarget().toString() + ")", LogLevel.ERROR); } try { try { //Get the label from the bind time environment, and put it in the current //environment. String label = b.getEnvironment().getEnv(GlobalEnv.class).GetLabel(); if(label == null){ //Set the permission to global if it's null, since that means //it wasn't set, and so we aren't in a secured environment anyways. label = Static.GLOBAL_PERMISSION; } env.getEnv(GlobalEnv.class).SetLabel(label); MethodScriptCompiler.execute(tree, env, null, null); } catch(CancelCommandException ex){ if(ex.getMessage() != null && !ex.getMessage().equals("")){ StreamUtils.GetSystemOut().println(ex.getMessage()); } } catch(FunctionReturnException ex){ //We simply allow this to end the event execution } catch(ProgramFlowManipulationException ex){ ConfigRuntimeException.HandleUncaughtException(new CREFormatException("Unexpected control flow operation used.", ex.getTarget()), env); } } finally { if(event != null){ event.stop(); } } try{ this.postExecution(env, activeEvent); } catch(UnsupportedOperationException e){ //Ignore. } } /** * This method is called before the event handling code is run, and provides a place * for the event code itself to modify the environment or active event data. * @param env The environment, at the time just before the event handler is called. * @param activeEvent The event handler code. * @throws UnsupportedOperationException If the preExecution isn't supported, this may * be thrown, and it will be ignored. */ public void preExecution(Environment env, BoundEvent.ActiveEvent activeEvent){ } /** * This method is called after the event handling code is run, and provides a place * for the event code itself to modify or cleanup the environment or active event data. * @param env The environment, at the time just before the event handler is called. * @param activeEvent The event handler code. * @throws UnsupportedOperationException If the preExecution isn't supported, this may * be thrown, and it will be ignored. */ public void postExecution(Environment env, BoundEvent.ActiveEvent activeEvent){ } /** * For sorting and optimizing events, we need a comparison operation. By default * it is compared by looking at the event name. * @param o * @return */ @Override public int compareTo(Event o) { return this.getName().compareTo(o.getName()); } /** * Since most events are minecraft events, we return true by default. * @return */ @Override public boolean supportsExternal(){ return true; } /** * If it is ok to by default do a simple conversion from a CArray to a * Map, this method can do it for you. Likely this is not acceptable, * so hard-coding the conversion will be necessary. * @param manualObject * @return */ public static Object DoConvert(CArray manualObject){ Map<String, Construct> map = new HashMap<String, Construct>(); for(String key : manualObject.stringKeySet()){ map.put(key, manualObject.get(key, Target.UNKNOWN)); } return map; } public Map<String, Construct> evaluate_helper(BindableEvent e) throws EventException{ return mixin.evaluate_helper(e); } /** * By default, this function triggers the event by calling the mixin * handler. If this is not the desired behavior, this method can be overridden * in the actual event (if it's an external event, for instance) * @param o */ @Override public void manualTrigger(BindableEvent o){ mixin.manualTrigger(o); } @Override public void cancel(BindableEvent o, boolean state){ mixin.cancel(o, state); } @Override public boolean isCancellable(BindableEvent o){ return mixin.isCancellable(o); } @Override public boolean isCancelled(BindableEvent o) { return mixin.isCancelled(o); } @Override public URL getSourceJar() { return ClassDiscovery.GetClassContainer(this.getClass()); } /** * Returns true if the event is annotated with @hide * @return */ @Override public final boolean appearInDocumentation() { return this.getClass().getAnnotation(hide.class) != null; } private final static Class[] EMPTY_CLASS = new Class[0]; @Override public Class<? extends Documentation>[] seeAlso() { return EMPTY_CLASS; } /** * Most events should return true for this, but passive events may override this * to return null. * @return */ @Override public boolean addCounter() { return true; } @Override public final boolean isCore() { Class c = this.getClass(); do{ if(c.getAnnotation(core.class) != null){ return true; } c = c.getDeclaringClass(); } while(c != null); return false; } }