package com.laytonsmith.core.functions; import com.laytonsmith.PureUtilities.Version; import com.laytonsmith.abstraction.StaticLayer; import com.laytonsmith.annotations.api; import com.laytonsmith.annotations.core; import com.laytonsmith.core.CHLog; import com.laytonsmith.core.CHVersion; import com.laytonsmith.core.LogLevel; import com.laytonsmith.core.Optimizable; import com.laytonsmith.core.ParseTree; import com.laytonsmith.core.Script; import com.laytonsmith.core.Static; import com.laytonsmith.core.compiler.FileOptions; import com.laytonsmith.core.constructs.CArray; import com.laytonsmith.core.constructs.CBoolean; import com.laytonsmith.core.constructs.CNull; import com.laytonsmith.core.constructs.CString; import com.laytonsmith.core.constructs.CVoid; import com.laytonsmith.core.constructs.Construct; import com.laytonsmith.core.constructs.IVariable; import com.laytonsmith.core.constructs.IVariableList; import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.environments.CommandHelperEnvironment; import com.laytonsmith.core.environments.Environment; import com.laytonsmith.core.environments.GlobalEnv; import com.laytonsmith.core.events.BoundEvent; import com.laytonsmith.core.events.BoundEvent.ActiveEvent; import com.laytonsmith.core.events.BoundEvent.Priority; import com.laytonsmith.core.events.Driver; import com.laytonsmith.core.events.Event; import com.laytonsmith.core.events.EventList; import com.laytonsmith.core.events.EventUtils; import com.laytonsmith.core.exceptions.CRE.CREBindException; import com.laytonsmith.core.exceptions.CRE.CRECastException; import com.laytonsmith.core.exceptions.CRE.CREInsufficientArgumentsException; import com.laytonsmith.core.exceptions.CRE.CREThrowable; import com.laytonsmith.core.exceptions.ConfigCompileException; import com.laytonsmith.core.exceptions.ConfigRuntimeException; import com.laytonsmith.core.exceptions.EventException; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; /** * */ @core public class EventBinding { public static String docs() { return "This class of functions provide methods to hook deep into the server's event architecture"; } private static final AtomicInteger bindCounter = new AtomicInteger(0); @api(environments = CommandHelperEnvironment.class) public static class bind extends AbstractFunction implements Optimizable { @Override public String getName() { return "bind"; } @Override public Integer[] numArgs() { return new Integer[]{Integer.MAX_VALUE}; } @Override public String docs() { return "string {event_name, options, prefilter, event_obj, [custom_params], <code>} Binds some functionality to an event, so that" + " when said event occurs, the event handler will fire. Returns the id of this event, so it can be unregistered" + " later, if need be."; } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CREBindException.class}; } @Override public boolean isRestricted() { return true; } @Override public boolean preResolveVariables() { return false; } @Override public CHVersion since() { return CHVersion.V3_3_0; } @Override public Boolean runAsync() { return false; } @Override public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { return CVoid.VOID; } @Override public Construct execs(Target t, Environment env, Script parent, ParseTree... nodes) { if (nodes.length < 5) { throw new CREInsufficientArgumentsException("bind accepts 5 or more parameters", t); } Construct name = parent.seval(nodes[0], env); Construct options = parent.seval(nodes[1], env); Construct prefilter = parent.seval(nodes[2], env); Construct event_obj = parent.eval(nodes[3], env); IVariableList custom_params = new IVariableList(); for (int a = 0; a < nodes.length - 5; a++) { Construct var = parent.eval(nodes[4 + a], env); if (!(var instanceof IVariable)) { throw new CRECastException("The custom parameters must be ivariables", t); } IVariable cur = (IVariable) var; custom_params.set(env.getEnv(GlobalEnv.class).GetVarList().get(cur.getVariableName(), cur.getTarget())); } Environment newEnv = env; try { newEnv = env.clone(); } catch (Exception e) { } newEnv.getEnv(GlobalEnv.class).SetVarList(custom_params); ParseTree tree = nodes[nodes.length - 1]; //Check to see if our arguments are correct if (!(options instanceof CNull || options instanceof CArray)) { throw new CRECastException("The options must be an array or null", t); } if (!(prefilter instanceof CNull || prefilter instanceof CArray)) { throw new CRECastException("The prefilters must be an array or null", t); } if (!(event_obj instanceof IVariable)) { throw new CRECastException("The event object must be an IVariable", t); } CString id; if (options instanceof CNull) { options = null; } if (prefilter instanceof CNull) { prefilter = null; } Event event; try { BoundEvent be = new BoundEvent(name.val(), (CArray) options, (CArray) prefilter, ((IVariable) event_obj).getVariableName(), newEnv, tree, t); EventUtils.RegisterEvent(be); id = new CString(be.getId(), t); event = EventList.getEvent(be.getEventName()); } catch (EventException ex) { throw new CREBindException(ex.getMessage(), t); } //Set up our bind counter, but only if the event is supposed to be added to the counter if (event.addCounter()) { synchronized (bindCounter) { if (bindCounter.get() == 0) { env.getEnv(GlobalEnv.class).GetDaemonManager().activateThread(null); StaticLayer.GetConvertor().addShutdownHook(new Runnable() { @Override public void run() { synchronized (bindCounter) { bindCounter.set(0); } } }); } bindCounter.incrementAndGet(); } } return id; } @Override public boolean useSpecialExec() { return true; } @Override public Set<OptimizationOption> optimizationOptions() { return EnumSet.of(OptimizationOption.OPTIMIZE_DYNAMIC, OptimizationOption.CUSTOM_LINK); } @Override public ParseTree optimizeDynamic(Target t, List<ParseTree> children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { if (children.size() < 5) { throw new CREInsufficientArgumentsException("bind accepts 5 or more parameters", t); } if (!children.get(0).isConst()) { // This ability may be removed in the future, to allow for better compilation checks of event type, once objects are added. // We'll add this warning to gauge impact. CHLog.GetLogger().Log(CHLog.Tags.COMPILER, LogLevel.WARNING, "Use of dynamic bind. This may be removed in the future, please" + " contact the developers to provide feedback if this affects you.", t); } return null; } @Override public void link(Target t, List<ParseTree> children) throws ConfigCompileException { String name = children.get(0).getData().val(); try { EventUtils.verifyEventName(name); } catch (IllegalArgumentException ex) { throw new ConfigCompileException(ex.getMessage(), t); } } } @api public static class dump_events extends AbstractFunction { @Override public String getName() { return "dump_events"; } @Override public Integer[] numArgs() { return new Integer[]{0}; } @Override public String docs() { return "array {} Returns an array of all the events currently registered on the server. Mostly meant for debugging," + " however it would be possible to parse this response to cherry pick events to unregister."; } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{}; } @Override public boolean isRestricted() { return true; } @Override public CHVersion since() { return CHVersion.V3_3_0; } @Override public Boolean runAsync() { return null; } @Override public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { return EventUtils.DumpEvents(); } } @api(environments = CommandHelperEnvironment.class) public static class unbind extends AbstractFunction { @Override public String getName() { return "unbind"; } @Override public Integer[] numArgs() { return new Integer[]{0, 1}; } @Override public String docs() { return "void {[eventID]} Unbinds an event, which causes it to not run anymore. If called from within an event handler, eventID is" + " optional, and defaults to the current event id."; } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CREBindException.class}; } @Override public boolean isRestricted() { return true; } @Override public CHVersion since() { return CHVersion.V3_3_0; } @Override public Boolean runAsync() { return null; } @Override public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { String id = null; if (args.length == 1) { //We are cancelling an arbitrary event id = args[0].val(); } else { //We are cancelling this event. If we are not in an event, throw an exception if (environment.getEnv(GlobalEnv.class).GetEvent() == null) { throw new CREBindException("No event ID specified, and not running inside an event", t); } id = environment.getEnv(GlobalEnv.class).GetEvent().getBoundEvent().getId(); } BoundEvent be = EventUtils.GetEventById(id); Event event = null; if (be != null) { event = be.getEventDriver(); } EventUtils.UnregisterEvent(id); //Only remove the counter if it had been added in the first place. if (event != null && event.addCounter()) { synchronized (bindCounter) { bindCounter.decrementAndGet(); if (bindCounter.get() == 0) { environment.getEnv(GlobalEnv.class).GetDaemonManager().deactivateThread(null); } } } return CVoid.VOID; } } @api(environments = CommandHelperEnvironment.class) public static class cancel extends AbstractFunction { @Override public String getName() { return "cancel"; } @Override public Integer[] numArgs() { return new Integer[]{0, 1}; } @Override public String docs() { return "void {[state]} Cancels the event (if applicable). If the event is not cancellable, or is already set to the specified" + " cancelled state, nothing happens." + " If called from outside an event handler, a BindException is thrown. By default, state is true, but you can" + " uncancel an event (if possible) by calling cancel(false)."; } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CREBindException.class}; } @Override public boolean isRestricted() { return true; } @Override public CHVersion since() { return CHVersion.V3_3_0; } @Override public Boolean runAsync() { return null; } @Override public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { boolean cancelled = true; if (args.length == 1) { cancelled = Static.getBoolean(args[0]); } BoundEvent.ActiveEvent original = environment.getEnv(GlobalEnv.class).GetEvent(); if (original == null) { throw new CREBindException("cancel cannot be called outside an event handler", t); } if (original.getUnderlyingEvent() != null && original.isCancellable()) { original.setCancelled(cancelled); } return CVoid.VOID; } } @api(environments = CommandHelperEnvironment.class) public static class is_cancelled extends AbstractFunction { @Override public String getName() { return "is_cancelled"; } @Override public Integer[] numArgs() { return new Integer[]{0}; } @Override public String docs() { return "boolean {} Returns whether or not the underlying event is cancelled or not. If the event is not cancellable in the first place," + " false is returned. If called from outside an event, a BindException is thrown"; } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CREBindException.class}; } @Override public boolean isRestricted() { return true; } @Override public CHVersion since() { return CHVersion.V3_3_0; } @Override public Boolean runAsync() { return false; } @Override public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { BoundEvent.ActiveEvent original = environment.getEnv(GlobalEnv.class).GetEvent(); if (original == null) { throw new CREBindException("is_cancelled cannot be called outside an event handler", t); } boolean result = false; if (original.getUnderlyingEvent() != null && original.isCancellable()) { result = original.isCancelled(); } return CBoolean.get(result); } } @api public static class trigger extends AbstractFunction { @Override public String getName() { return "trigger"; } @Override public Integer[] numArgs() { return new Integer[]{2, 3}; } @Override public String docs() { return "void {eventName, eventObject, [serverWide]} Manually triggers bound events. The event object passed to this function is " + " sent directly as-is to the bound events. Check the documentation for each event to see what is required." + " No checks will be done on the data here, but it is not recommended to fail to send all parameters required." + " If serverWide is true, the event is triggered directly in the server, unless it is a CommandHelper specific" + " event, in which case, serverWide is irrelevant. Defaults to false, which means that only CommandHelper code" + " will receive the event."; } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CRECastException.class}; } @Override public boolean isRestricted() { return true; } @Override public CHVersion since() { return CHVersion.V3_3_0; } @Override public Boolean runAsync() { return false; } @Override public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { CArray obj = null; if (args[1] instanceof CNull) { obj = new CArray(t); } else if (args[1] instanceof CArray) { obj = (CArray) args[1]; } else { throw new CRECastException("The eventObject must be null, or an array", t); } boolean serverWide = false; if (args.length == 3) { serverWide = Static.getBoolean(args[2]); } EventUtils.ManualTrigger(args[0].val(), obj, t, serverWide); return CVoid.VOID; } } @api(environments = CommandHelperEnvironment.class) public static class modify_event extends AbstractFunction { @Override public String getName() { return "modify_event"; } @Override public Integer[] numArgs() { return new Integer[]{2}; } @Override public String docs() { return "boolean {parameter, value, [throwOnFailure]} Modifies the underlying event object, if applicable." + " The documentation for each event will explain what parameters can be modified," + " and what their expected values are. ---- If an invalid parameter name is passed in," + " nothing will happen. If this function is called from outside an event" + " handler, a BindException is thrown. Note that modifying the underlying event" + " will NOT update the event object passed in to the event handler. The function returns" + " whether or not the parameter was updated successfully. It could fail to modify the" + " event if a higher priority handler has locked this parameter, or if updating the underlying" + " event failed. If throwOnFailure is true, instead of returning false, it will throw" + " a BindException. The default for throwOnFailure is false. If a monitor level handler" + " even attempts to modify an event, an exception will be thrown."; } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CRECastException.class, CREBindException.class}; } @Override public boolean isRestricted() { return true; } @Override public CHVersion since() { return CHVersion.V3_3_0; } @Override public Boolean runAsync() { return false; } @Override public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { String parameter = args[0].val(); Construct value = args[1]; boolean throwOnFailure = false; if (args.length == 3) { throwOnFailure = Static.getBoolean(args[3]); } if (environment.getEnv(GlobalEnv.class).GetEvent() == null) { throw new CREBindException(this.getName() + " must be called from within an event handler", t); } Event e = environment.getEnv(GlobalEnv.class).GetEvent().getEventDriver(); if (environment.getEnv(GlobalEnv.class).GetEvent().getBoundEvent().getPriority().equals(Priority.MONITOR)) { throw new CREBindException("Monitor level handlers may not modify an event!", t); } ActiveEvent active = environment.getEnv(GlobalEnv.class).GetEvent(); boolean success = false; if (!active.isLocked(parameter)) { try { success = e.modifyEvent(parameter, value, environment.getEnv(GlobalEnv.class).GetEvent().getUnderlyingEvent()); } catch (ConfigRuntimeException ex) { ex.setTarget(t); throw ex; } } else { success = false; } if (throwOnFailure && !success) { throw new CREBindException("Event parameter is already locked!", t); } return CBoolean.get(success); } } @api(environments = CommandHelperEnvironment.class) public static class lock extends AbstractFunction { @Override public String getName() { return "lock"; } @Override public Integer[] numArgs() { return new Integer[]{Integer.MAX_VALUE}; } @Override public String docs() { return "void {<none> | parameterArray | parameter, [parameter...]} Locks the specified event parameter(s), or all of them," + " if specified with no arguments. Locked parameters become read only for lower priority event handlers."; } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CREBindException.class}; } @Override public boolean isRestricted() { return true; } @Override public Boolean runAsync() { return false; } @Override public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { if (environment.getEnv(GlobalEnv.class).GetEvent() == null) { throw new CREBindException("lock must be called from within an event handler", t); } BoundEvent.ActiveEvent e = environment.getEnv(GlobalEnv.class).GetEvent(); Priority p = e.getBoundEvent().getPriority(); List<String> params = new ArrayList<String>(); if (args.length == 0) { e.lock(null); } else { if (args[0] instanceof CArray) { CArray ca = (CArray) args[1]; for (int i = 0; i < ca.size(); i++) { params.add(ca.get(i, t).val()); } } else { for (int i = 0; i < args.length; i++) { params.add(args[i].val()); } } } for (String param : params) { e.lock(param); } return CVoid.VOID; } @Override public CHVersion since() { return CHVersion.V3_3_0; } } @api(environments = CommandHelperEnvironment.class) public static class is_locked extends AbstractFunction { @Override public String getName() { return "is_locked"; } @Override public Integer[] numArgs() { return new Integer[]{1}; } @Override public String docs() { return "boolean {parameter} Returns whether or not a call to modify_event() would fail, based on" + " the parameter being locked by a higher priority handler. If this returns false, it" + " is still not a guarantee that the event would be successfully modified, just that" + " it isn't locked."; } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CREBindException.class}; } @Override public boolean isRestricted() { return true; } @Override public Boolean runAsync() { return false; } @Override public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { if (environment.getEnv(GlobalEnv.class).GetEvent() == null) { throw new CREBindException("is_locked may only be called from inside an event handler", t); } return CBoolean.get(environment.getEnv(GlobalEnv.class).GetEvent().isLocked(args[0].val())); } @Override public CHVersion since() { return CHVersion.V3_3_0; } } @api(environments = CommandHelperEnvironment.class) public static class consume extends AbstractFunction { @Override public String getName() { return "consume"; } @Override public Integer[] numArgs() { return new Integer[]{0}; } @Override public String docs() { return "void {} Consumes an event, so that lower priority handlers don't even" + " recieve the event. Monitor level handlers will still recieve it, however," + " and they can check to see if the event was consumed."; } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CREBindException.class}; } @Override public boolean isRestricted() { return true; } @Override public Boolean runAsync() { return false; } @Override public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { if (environment.getEnv(GlobalEnv.class).GetEvent() == null) { throw new CREBindException("consume may only be called from an event handler!", t); } environment.getEnv(GlobalEnv.class).GetEvent().consume(); return CVoid.VOID; } @Override public CHVersion since() { return CHVersion.V3_3_0; } } @api(environments = CommandHelperEnvironment.class) public static class is_consumed extends AbstractFunction { @Override public String getName() { return "is_consumed"; } @Override public Integer[] numArgs() { return new Integer[]{0}; } @Override public String docs() { return "boolean {} Returns whether or not this event has been consumed. Usually only useful" + " for Monitor level handlers, it could also be used for highly robust code," + " as an equal priority handler could have consumed the event, but this handler" + " would still recieve it."; } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CREBindException.class}; } @Override public boolean isRestricted() { return true; } @Override public Boolean runAsync() { return false; } @Override public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { if (environment.getEnv(GlobalEnv.class).GetEvent() == null) { throw new CREBindException("is_consumed must be called from within an event handler", t); } return CBoolean.get(environment.getEnv(GlobalEnv.class).GetEvent().isConsumed()); } @Override public CHVersion since() { return CHVersion.V3_3_0; } } // @api public static class when_triggered extends AbstractFunction{ // // } // @api public static class when_cancelled extends AbstractFunction{ // // } @api(environments = CommandHelperEnvironment.class) public static class event_meta extends AbstractFunction { @Override public String getName() { return "event_meta"; } @Override public Integer[] numArgs() { return new Integer[]{0}; } @Override public String docs() { return "array {} Returns meta information about the activity in regards to this event. This" + " is meant as a debug tool."; } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CREBindException.class}; } @Override public boolean isRestricted() { return true; } @Override public Boolean runAsync() { return false; } @Override public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { if (environment.getEnv(GlobalEnv.class).GetEvent() == null) { throw new CREBindException("event_meta must be called from within an event handler!", t); } CArray history = new CArray(t); for (String entry : environment.getEnv(GlobalEnv.class).GetEvent().getHistory()) { history.push(new CString(entry, t), t); } return history; } @Override public CHVersion since() { return CHVersion.V3_3_0; } } @api public static class has_bind extends AbstractFunction { @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{}; } @Override public boolean isRestricted() { return true; } @Override public Boolean runAsync() { return null; } @Override public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { String id = args[0].val(); for (Driver d : Driver.values()) { Set<BoundEvent> events = EventUtils.GetEvents(d); if (events != null) { for (BoundEvent b : events) { if (b.getId().equals(id)) { return CBoolean.TRUE; } } } } return CBoolean.FALSE; } @Override public String getName() { return "has_bind"; } @Override public Integer[] numArgs() { return new Integer[]{1}; } @Override public String docs() { return "boolean {id} Returns true if a bind with the specified id exists and is" + " currently bound. False is returned otherwise. This can be used to" + " pre-emptively avoid a BindException if duplicate ids are used."; } @Override public Version since() { return CHVersion.V3_3_1; } } }