001package com.github.theholywaffle.teamspeak3; 002 003/* 004 * #%L 005 * TeamSpeak 3 Java API 006 * %% 007 * Copyright (C) 2014 Bert De Geyter 008 * %% 009 * Permission is hereby granted, free of charge, to any person obtaining a copy 010 * of this software and associated documentation files (the "Software"), to deal 011 * in the Software without restriction, including without limitation the rights 012 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 013 * copies of the Software, and to permit persons to whom the Software is 014 * furnished to do so, subject to the following conditions: 015 * 016 * The above copyright notice and this permission notice shall be included in 017 * all copies or substantial portions of the Software. 018 * 019 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 020 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 021 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 022 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 023 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 024 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 025 * THE SOFTWARE. 026 * #L% 027 */ 028 029import com.github.theholywaffle.teamspeak3.api.event.*; 030import com.github.theholywaffle.teamspeak3.api.exception.TS3UnknownEventException; 031import com.github.theholywaffle.teamspeak3.api.wrapper.Wrapper; 032import com.github.theholywaffle.teamspeak3.commands.response.DefaultArrayResponse; 033import org.slf4j.Logger; 034import org.slf4j.LoggerFactory; 035 036import java.util.ArrayDeque; 037import java.util.Collection; 038import java.util.Iterator; 039import java.util.Map; 040import java.util.Queue; 041import java.util.concurrent.CopyOnWriteArrayList; 042 043public class EventManager { 044 045 private static final Logger log = LoggerFactory.getLogger(EventManager.class); 046 047 // CopyOnWriteArrayList for thread safety 048 private final Collection<ListenerTask> tasks = new CopyOnWriteArrayList<>(); 049 private final TS3Query ts3; 050 051 EventManager(TS3Query query) { 052 ts3 = query; 053 } 054 055 public void addListeners(TS3Listener... listeners) { 056 for (TS3Listener listener : listeners) { 057 if (listener == null) throw new IllegalArgumentException("A listener was null"); 058 ListenerTask task = new ListenerTask(listener); 059 tasks.add(task); 060 } 061 } 062 063 public void removeListeners(TS3Listener... listeners) { 064 // Bad performance (O(n*m)), but this method is rarely if ever used 065 Iterator<ListenerTask> taskIterator = tasks.iterator(); 066 while (taskIterator.hasNext()) { 067 ListenerTask task = taskIterator.next(); 068 TS3Listener taskListener = task.getListener(); 069 070 for (TS3Listener listener : listeners) { 071 if (taskListener.equals(listener)) { 072 taskIterator.remove(); 073 break; 074 } 075 } 076 } 077 } 078 079 public void fireEvent(String notifyName, String notifyBody) { 080 final DefaultArrayResponse response = DefaultArrayResponse.parse(notifyBody); 081 082 for (Wrapper dataWrapper : response.getResponses()) { 083 Map<String, String> eventData = dataWrapper.getMap(); 084 TS3Event event = createEvent(notifyName, eventData); 085 086 fireEvent(event); 087 } 088 } 089 090 public void fireEvent(TS3Event event) { 091 if (event == null) throw new IllegalArgumentException("TS3Event was null"); 092 for (ListenerTask task : tasks) { 093 task.enqueueEvent(event); 094 } 095 } 096 097 private static TS3Event createEvent(String notifyName, Map<String, String> eventData) { 098 switch (notifyName) { 099 case "notifytextmessage": 100 return new TextMessageEvent(eventData); 101 case "notifycliententerview": 102 return new ClientJoinEvent(eventData); 103 case "notifyclientleftview": 104 return new ClientLeaveEvent(eventData); 105 case "notifyserveredited": 106 return new ServerEditedEvent(eventData); 107 case "notifychanneledited": 108 return new ChannelEditedEvent(eventData); 109 case "notifychanneldescriptionchanged": 110 return new ChannelDescriptionEditedEvent(eventData); 111 case "notifyclientmoved": 112 return new ClientMovedEvent(eventData); 113 case "notifychannelcreated": 114 return new ChannelCreateEvent(eventData); 115 case "notifychanneldeleted": 116 return new ChannelDeletedEvent(eventData); 117 case "notifychannelmoved": 118 return new ChannelMovedEvent(eventData); 119 case "notifychannelpasswordchanged": 120 return new ChannelPasswordChangedEvent(eventData); 121 case "notifytokenused": 122 return new PrivilegeKeyUsedEvent(eventData); 123 default: 124 throw new TS3UnknownEventException(notifyName + " " + eventData); 125 } 126 } 127 128 /* 129 * Do not synchronize on instances of this class from outside the class itself! 130 */ 131 private class ListenerTask implements Runnable { 132 133 private static final int START_QUEUE_SIZE = 16; 134 135 private final TS3Listener listener; 136 private final Queue<TS3Event> eventQueue; 137 138 ListenerTask(TS3Listener ts3Listener) { 139 listener = ts3Listener; 140 eventQueue = new ArrayDeque<>(START_QUEUE_SIZE); 141 } 142 143 TS3Listener getListener() { 144 return listener; 145 } 146 147 synchronized void enqueueEvent(TS3Event event) { 148 if (eventQueue.isEmpty()) { 149 // Add the event to the queue and start a task to process this event and any events 150 // that might be enqueued before the last event is removed from the queue 151 eventQueue.add(event); 152 ts3.submitUserTask("Event listener task", this); 153 } else { 154 // Just add the event to the queue, the running task will pick it up 155 eventQueue.add(event); 156 } 157 } 158 159 @Override 160 public void run() { 161 TS3Event currentEvent; 162 synchronized (this) { 163 currentEvent = eventQueue.peek(); 164 if (currentEvent == null) throw new IllegalStateException("Task started without events"); 165 } 166 167 do { 168 try { 169 currentEvent.fire(listener); 170 } catch (Throwable throwable) { 171 log.error("Event listener threw an exception", throwable); 172 } 173 174 synchronized (this) { 175 eventQueue.remove(); 176 currentEvent = eventQueue.peek(); 177 } 178 } while (currentEvent != null); 179 } 180 } 181}