/* * This file is part of Skript. * * Skript is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Skript is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Skript. If not, see <http://www.gnu.org/licenses/>. * * * Copyright 2011-2014 Peter Güttinger * */ package ch.njol.skript; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import org.bukkit.Bukkit; import org.bukkit.event.Cancellable; import org.bukkit.event.Event; import org.bukkit.event.Event.Result; import org.bukkit.event.Listener; import org.bukkit.event.block.Action; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.server.ServerCommandEvent; import org.bukkit.plugin.EventExecutor; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.ScriptLoader.ScriptInfo; import ch.njol.skript.command.Commands; import ch.njol.skript.lang.SelfRegisteringSkriptEvent; import ch.njol.skript.lang.Trigger; import ch.njol.skript.lang.function.Functions; /** * @author Peter Güttinger */ public abstract class SkriptEventHandler { private SkriptEventHandler() {} final static Map<Class<? extends Event>, List<Trigger>> triggers = new HashMap<Class<? extends Event>, List<Trigger>>(); private final static List<Trigger> selfRegisteredTriggers = new ArrayList<Trigger>(); private final static Iterator<Trigger> getTriggers(final Class<? extends Event> event) { return new Iterator<Trigger>() { @Nullable private Class<?> e = event; @Nullable private Iterator<Trigger> current = null; @Override public boolean hasNext() { Iterator<Trigger> current = this.current; Class<?> e = this.e; while (current == null || !current.hasNext()) { if (e == null || !Event.class.isAssignableFrom(e)) return false; final List<Trigger> l = triggers.get(e); this.current = current = l == null ? null : l.iterator(); this.e = e = e.getSuperclass(); } return true; } @Override public Trigger next() { final Iterator<Trigger> current = this.current; if (current == null || !hasNext()) throw new NoSuchElementException(); final Trigger next = current.next(); assert next != null; return next; } @Override public void remove() { throw new UnsupportedOperationException(); } }; } @Nullable static Event last = null; final static EventExecutor ee = new EventExecutor() { @Override public void execute(final @Nullable Listener l, final @Nullable Event e) { if (e == null) return; if (last == e) // an event is received multiple times if multiple superclasses of it are registered return; last = e; check(e); } }; static void check(final Event e) { @SuppressWarnings("null") Iterator<Trigger> ts = getTriggers(e.getClass()); if (!ts.hasNext()) return; if (Skript.logVeryHigh()) { boolean hasTrigger = false; while (ts.hasNext()) { if (ts.next().getEvent().check(e)) { hasTrigger = true; break; } } if (!hasTrigger) return; final Class<? extends Event> c = e.getClass(); assert c != null; ts = getTriggers(c); logEventStart(e); } if (e instanceof Cancellable && ((Cancellable) e).isCancelled() && !(e instanceof PlayerInteractEvent && (((PlayerInteractEvent) e).getAction() == Action.LEFT_CLICK_AIR || ((PlayerInteractEvent) e).getAction() == Action.RIGHT_CLICK_AIR) && ((PlayerInteractEvent) e).useItemInHand() != Result.DENY) || e instanceof ServerCommandEvent && (((ServerCommandEvent) e).getCommand() == null || ((ServerCommandEvent) e).getCommand().isEmpty())) { if (Skript.logVeryHigh()) Skript.info(" -x- was cancelled"); return; } while (ts.hasNext()) { final Trigger t = ts.next(); if (!t.getEvent().check(e)) continue; logTriggerStart(t); t.execute(e); logTriggerEnd(t); } logEventEnd(); } private static long startEvent; public static void logEventStart(final Event e) { if (!Skript.logVeryHigh()) return; startEvent = System.nanoTime(); Skript.info(""); Skript.info("== " + e.getClass().getName() + " =="); } public static void logEventEnd() { if (!Skript.logVeryHigh()) return; Skript.info("== took " + 1. * (System.nanoTime() - startEvent) / 1000000. + " milliseconds =="); } static long startTrigger; public static void logTriggerStart(final Trigger t) { if (!Skript.logVeryHigh()) return; Skript.info("# " + t.getName()); startTrigger = System.nanoTime(); } public static void logTriggerEnd(final Trigger t) { if (!Skript.logVeryHigh()) return; Skript.info("# " + t.getName() + " took " + 1. * (System.nanoTime() - startTrigger) / 1000000. + " milliseconds"); } static void addTrigger(final Class<? extends Event>[] events, final Trigger trigger) { for (final Class<? extends Event> e : events) { List<Trigger> ts = triggers.get(e); if (ts == null) triggers.put(e, ts = new ArrayList<Trigger>()); ts.add(trigger); } } /** * Stores a self registered trigger to allow for it to be unloaded later on. * * @param t Trigger that has already been registered to its event */ static void addSelfRegisteringTrigger(final Trigger t) { assert t.getEvent() instanceof SelfRegisteringSkriptEvent; selfRegisteredTriggers.add(t); } static ScriptInfo removeTriggers(final File script) { final ScriptInfo info = new ScriptInfo(); info.files = 1; final Iterator<List<Trigger>> triggersIter = SkriptEventHandler.triggers.values().iterator(); while (triggersIter.hasNext()) { final List<Trigger> ts = triggersIter.next(); for (int i = 0; i < ts.size(); i++) { if (script.equals(ts.get(i).getScript())) { info.triggers++; ts.remove(i); i--; if (ts.isEmpty()) triggersIter.remove(); } } } for (int i = 0; i < selfRegisteredTriggers.size(); i++) { final Trigger t = selfRegisteredTriggers.get(i); if (script.equals(t.getScript())) { info.triggers++; ((SelfRegisteringSkriptEvent) t.getEvent()).unregister(t); selfRegisteredTriggers.remove(i); i--; } } info.commands = Commands.unregisterCommands(script); info.functions = Functions.clearFunctions(script); return info; } static void removeAllTriggers() { triggers.clear(); for (final Trigger t : selfRegisteredTriggers) ((SelfRegisteringSkriptEvent) t.getEvent()).unregisterAll(); selfRegisteredTriggers.clear(); // unregisterEvents(); } /** * Stores which events are currently registered with Bukkit */ private final static Set<Class<? extends Event>> registeredEvents = new HashSet<Class<? extends Event>>(); private final static Listener listener = new Listener() {}; @SuppressWarnings({"unchecked", "rawtypes"}) final static void registerBukkitEvents() { for (final Class<? extends Event> e : triggers.keySet()) { assert e != null; if (!containsSuperclass((Set) registeredEvents, e)) { // I just love Java's generics Bukkit.getPluginManager().registerEvent(e, listener, SkriptConfig.defaultEventPriority.value(), ee, Skript.getInstance()); registeredEvents.add(e); // for (final Iterator<Class<? extends Event>> i = registeredEvents.iterator(); i.hasNext();) { // final Class<? extends Event> ev = i.next(); // if (e.isAssignableFrom(ev)) { // if (unregisterEvent(ev)) // i.remove(); // } // } } } } public final static boolean containsSuperclass(final Set<Class<?>> classes, final Class<?> c) { if (classes.contains(c)) return true; for (final Class<?> cl : classes) { if (cl.isAssignableFrom(c)) return true; } return false; } // private final static void unregisterEvents() { // for (final Iterator<Class<? extends Event>> i = registeredEvents.iterator(); i.hasNext();) { // if (unregisterEvent(i.next())) // i.remove(); // } // } // // private final static boolean unregisterEvent(Class<? extends Event> event) { // try { // Method m = null; // while (m == null) { // try { // m = event.getDeclaredMethod("getHandlerList"); // } catch (NoSuchMethodException e) { // event = (Class<? extends Event>) event.getSuperclass(); // if (event == Event.class) { // assert false; // return false; // } // } // } // m.setAccessible(true); // final HandlerList l = (HandlerList) m.invoke(null); // l.unregister(listener); // return true; // } catch (final Exception e) { // if (Skript.testing()) // e.printStackTrace(); // } // return false; // } }