/*
* Copyright 2016 the original author or authors.
*
* This file is part of HotswapAgent.
*
* HotswapAgent 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 2 of the License, or (at your
* option) any later version.
*
* HotswapAgent 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 HotswapAgent. If not, see http://www.gnu.org/licenses/.
*/
package org.hotswap.agent.watch.nio;
import java.nio.file.Path;
import java.nio.file.WatchEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import org.hotswap.agent.logging.AgentLogger;
import org.hotswap.agent.watch.WatchEventListener;
import org.hotswap.agent.watch.WatchFileEvent;
/**
* The EventDispatcher holds a queue of all events collected by the watcher but
* not yet processed. It runs on its own thread and is responsible for calling
* all the registered listeners.
*
* Since file system events can spawn too fast, this implementation works as
* buffer for fast spawning events. The watcher is now responsible for
* collecting and pushing events in this queue.
*
*/
public class EventDispatcher implements Runnable {
/** The logger. */
protected AgentLogger LOGGER = AgentLogger.getLogger(this.getClass());
/**
* The Class Event.
*/
static class Event {
/** The event. */
final WatchEvent<Path> event;
/** The path. */
final Path path;
/**
* Instantiates a new event.
*
* @param event
* the event
* @param path
* the path
*/
public Event(WatchEvent<Path> event, Path path) {
super();
this.event = event;
this.path = path;
}
}
/** The map of listeners. This is managed by the watcher service*/
private final Map<Path, List<WatchEventListener>> listeners;
/** The working queue. The event queue is drained and all pending events are added in this list */
private final ArrayList<Event> working = new ArrayList<>();
/** The runnable. */
private Thread runnable = null;
/**
* Instantiates a new event dispatcher.
*
* @param listeners
* the listeners
*/
public EventDispatcher(Map<Path, List<WatchEventListener>> listeners) {
super();
this.listeners = listeners;
}
/** The event queue. */
private final ArrayBlockingQueue<Event> eventQueue = new ArrayBlockingQueue<>(500);
/*
* (non-Javadoc)
*
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
/*
* The algorithm is naive:
* a) work with not processed (in case);
* b) drain the queue
* c) work on newly collected
* d) empty working queue
*/
while (true) {
// finish any pending ones
for (Event e : working) {
callListeners(e.event, e.path);
if (Thread.interrupted()) {
return;
}
Thread.yield();
}
// drain the event queue
eventQueue.drainTo(working);
// work on new events.
for (Event e : working) {
callListeners(e.event, e.path);
if (Thread.interrupted()) {
return;
}
Thread.yield();
}
// crear the working queue.
working.clear();
try {
Thread.sleep(50);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
return;
}
}
}
/**
* Adds the.
*
* @param event
* the event
* @param path
* the path
*/
public void add(WatchEvent<Path> event, Path path) {
eventQueue.offer(new Event(event, path));
}
/**
* Call the listeners.
* Listeners are organized per path in a Map. The number of paths is low so a simple iteration should be fast enough.
*
* @param event
* the event
* @param path
* the path
*/
// notify listeners about new event
private void callListeners(final WatchEvent<?> event, final Path path) {
boolean matchedOne = false;
for (Map.Entry<Path, List<WatchEventListener>> list : listeners.entrySet()) {
if (path.startsWith(list.getKey())) {
matchedOne = true;
for (WatchEventListener listener : new ArrayList<>(list.getValue())) {
WatchFileEvent agentEvent = new HotswapWatchFileEvent(event, path);
try {
listener.onEvent(agentEvent);
} catch (Throwable e) {
// LOGGER.error("Error in watch event '{}' listener
// '{}'", e, agentEvent, listener);
}
}
}
}
if (!matchedOne) {
LOGGER.error("No match for watch event '{}', path '{}'", event, path);
}
}
/**
* Start.
*/
public void start() {
runnable = new Thread(this);
runnable.setDaemon(true);
runnable.setName("HotSwap Dispatcher");
runnable.start();
}
/**
* Stop.
*
* @throws InterruptedException
* the interrupted exception
*/
public void stop() throws InterruptedException {
if (runnable != null) {
runnable.interrupt();
runnable.join();
}
runnable = null;
}
}