package com.hypirion.beckon; import sun.misc.Signal; import sun.misc.SignalHandler; import java.util.List; import java.util.ArrayList; import java.util.Map; import java.util.HashMap; import clojure.lang.PersistentHashSet; import clojure.lang.Seqable; public class SignalRegistererHelper { /** * A set of modified signal handlers. */ private final static Map<String, SignalHandler> originalHandlers = new HashMap<String, SignalHandler>(); /** * Registers the new list of functions to the signal name, and returns the * old SignalHandler. */ private static SignalHandler setHandler(String signame, Seqable fns) { Signal sig = new Signal(signame); SignalFolder folder = new SignalFolder(fns); SignalHandler oldHandler = Signal.handle(sig, folder); return oldHandler; } /** * Registers the signal name to a List of Runnables, where each callable * returns an Object. The signal handling is performed as follows: The first * callable is called, and if it returns a value equal to <code>false</code> * or <code>null</code> it will stop. Otherwise it will repeat on the next * callable, until there are no more left. * * @param signame the signal name to register this list of callables on. * @param fns the list of Runnables to (potentially) call. */ static synchronized void register(String signame, Seqable fns) { SignalHandler old = setHandler(signame, fns); if (!originalHandlers.containsKey(signame)) { originalHandlers.put(signame, old); } } /** * Resets/reinits the signal to be handled by its original signal handler. * * @param signame the name of the signal to reinit. */ static synchronized void resetDefaultHandler(String signame) throws SignalHandlerNotFoundException { if (originalHandlers.containsKey(signame)) { SignalHandler original = originalHandlers.get(signame); Signal sig = new Signal(signame); Signal.handle(sig, original); originalHandlers.remove(sig); SignalAtoms.getSignalAtom(signame).reset(getHandlerSeq(signame)); // As the Atom has a watch which calls register, the handle has been // modified again. Perform another handle call to fix this: Signal.handle(sig, original); } } /** * Resets/reinits all the signals back to their original signal handlers, * discarding all possible changes done to them. */ static synchronized void resetAll() throws SignalHandlerNotFoundException { // To get around the fact that we cannot remove elements from a set // while iterating over it. List<String> signames = new ArrayList<String>(originalHandlers.keySet()); for (String signame : signames) { resetDefaultHandler(signame); } } /** * Returns a set of Runnables which is used within the SignalFolder * handling the Signal, or a PersistentSet with a Runnable SignalHandler if * the SignalHandler is not a SignalFolder. * * @param signame The name of the Signal. * * @return A list with the Runnables used in the SignalFolder. */ static synchronized Seqable getHandlerSeq(String signame) { Signal sig = new Signal(signame); // Urgh, no easy way to get current signal handler. // Double-handle to get current one without issues. SignalHandler current = Signal.handle(sig, SignalHandler.SIG_DFL); Signal.handle(sig, current); if (current instanceof SignalFolder) { return ((SignalFolder)current).originalList; } else { Runnable wrappedHandler = new RunnableSignalHandler(sig, current); return PersistentHashSet.create(wrappedHandler); } } /** * A Runnable SignalHandler is simply a Runnable which wraps a * SignalHandler. This is used internally to ensure that people can perform * <code>swap!</code> in Clojure programs without worrying that the default * SignalHandler will cause issues as it's not Runnable by default. */ private static class RunnableSignalHandler implements Runnable { private final Signal sig; private final SignalHandler handler; /** * Returns a Runnable which will call <code>handler.handle(sig)</code> * whenever called. */ RunnableSignalHandler(Signal sig, SignalHandler handler) { this.sig = sig; this.handler = handler; } /** * Calls the SignalHandler with the signal provided at construction, and * returns true if the handler doesn't cast any exception. If the * handler cast an exception, false is returned, and if the handler * casts an error, that error is cast. * * @return true if the handler doesn't throw an exception, false * otherwise. */ @Override public void run() { handler.handle(sig); } } static void raise(String signame) { Signal sig = new Signal(signame); Signal.raise(sig); } }