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}