[beckon-clojure] 01/02: New upstream version 0.1.1

Apollon Oikonomopoulos apoikos at moszumanska.debian.org
Fri Aug 4 22:34:53 UTC 2017


This is an automated email from the git hooks/post-receive script.

apoikos pushed a commit to branch master
in repository beckon-clojure.

commit 7a3915c4985eb8d5a18b7fef2fcf203fc736827d
Author: Apollon Oikonomopoulos <apoikos at debian.org>
Date:   Fri Aug 4 18:21:59 2017 -0400

    New upstream version 0.1.1
---
 .gitignore                                         |  13 ++
 CHANGES.md                                         |  18 ++
 LICENSE                                            | 198 +++++++++++++++++++++
 README.md                                          | 150 ++++++++++++++++
 doc/intro.md                                       |   3 +
 project.clj                                        |  13 ++
 src/clojure/beckon.clj                             |  45 +++++
 src/java/com/hypirion/beckon/SignalAtoms.java      |  97 ++++++++++
 src/java/com/hypirion/beckon/SignalFolder.java     |  42 +++++
 .../beckon/SignalHandlerNotFoundException.java     |   9 +
 src/java/com/hypirion/beckon/SignalRegisterer.java |  55 ++++++
 .../hypirion/beckon/SignalRegistererHelper.java    | 144 +++++++++++++++
 test/beckon/core_test.clj                          |   7 +
 13 files changed, 794 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c8bea18
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,13 @@
+/target
+/lib
+/classes
+/checkouts
+pom.xml
+pom.xml.asc
+*.jar
+*.class
+.lein-deps-sum
+.lein-failures
+.lein-plugins
+.lein-repl-history
+*~
diff --git a/CHANGES.md b/CHANGES.md
new file mode 100644
index 0000000..3fc3bcb
--- /dev/null
+++ b/CHANGES.md
@@ -0,0 +1,18 @@
+# beckon changelog
+
+## 0.1.1 [`docs`][0.1.0-docs] [`tag`][0.1.1-tag]
+
+* Beckon will now always compile to 1.6-compliant bytecode.
+
+## 0.1.0 [`docs`][0.1.0-docs] [`tag`][0.1.0-tag]
+
+* **New:** The function `signal-atom` returns an atom containing a collection of
+  functions. The functions will be invoked sequentially whenever a signal is
+  trapped.
+* **New:** The function `raise!` raises a signal.
+* **New:** `reinit!` and `reinit-all!` reinitializes signal handlers, and return
+  them to their "factory settings".
+
+[0.1.1-tag]: https://github.com/hyPiRion/beckon/tree/0.1.1
+[0.1.0-tag]: https://github.com/hyPiRion/beckon/tree/0.1.0
+[0.1.0-docs]: http://hypirion.github.com/beckon/0.1.0/
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..11ecb79
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,198 @@
+Eclipse Public License - v 1.0
+
+THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
+LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
+CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+1. DEFINITIONS
+
+"Contribution" means:
+
+a) in the case of the initial Contributor, the initial code and documentation
+   distributed under this Agreement, and
+b) in the case of each subsequent Contributor:
+    i) changes to the Program, and
+   ii) additions to the Program;
+
+   where such changes and/or additions to the Program originate from and are
+   distributed by that particular Contributor. A Contribution 'originates' from
+   a Contributor if it was added to the Program by such Contributor itself or
+   anyone acting on such Contributor's behalf. Contributions do not include
+   additions to the Program which: (i) are separate modules of software
+   distributed in conjunction with the Program under their own license
+   agreement, and (ii) are not derivative works of the Program.
+
+"Contributor" means any person or entity that distributes the Program.
+
+"Licensed Patents" mean patent claims licensable by a Contributor which are
+necessarily infringed by the use or sale of its Contribution alone or when
+combined with the Program.
+
+"Program" means the Contributions distributed in accordance with this Agreement.
+
+"Recipient" means anyone who receives the Program under this Agreement,
+including all Contributors.
+
+2. GRANT OF RIGHTS
+  a) Subject to the terms of this Agreement, each Contributor hereby grants
+     Recipient a non-exclusive, worldwide, royalty-free copyright license to
+     reproduce, prepare derivative works of, publicly display, publicly perform,
+     distribute and sublicense the Contribution of such Contributor, if any, and
+     such derivative works, in source code and object code form.
+  b) Subject to the terms of this Agreement, each Contributor hereby grants
+     Recipient a non-exclusive, worldwide, royalty-free patent license under
+     Licensed Patents to make, use, sell, offer to sell, import and otherwise
+     transfer the Contribution of such Contributor, if any, in source code and
+     object code form. This patent license shall apply to the combination of the
+     Contribution and the Program if, at the time the Contribution is added by
+     the Contributor, such addition of the Contribution causes such combination
+     to be covered by the Licensed Patents. The patent license shall not apply
+     to any other combinations which include the Contribution. No hardware per
+     se is licensed hereunder.
+  c) Recipient understands that although each Contributor grants the licenses to
+     its Contributions set forth herein, no assurances are provided by any
+     Contributor that the Program does not infringe the patent or other
+     intellectual property rights of any other entity. Each Contributor
+     disclaims any liability to Recipient for claims brought by any other entity
+     based on infringement of intellectual property rights or otherwise. As a
+     condition to exercising the rights and licenses granted hereunder, each
+     Recipient hereby assumes sole responsibility to secure any other
+     intellectual property rights needed, if any. For example, if a third party
+     patent license is required to allow Recipient to distribute the Program, it
+     is Recipient's responsibility to acquire that license before distributing
+     the Program.
+  d) Each Contributor represents that to its knowledge it has sufficient
+     copyright rights in its Contribution, if any, to grant the copyright
+     license set forth in this Agreement.
+
+3. REQUIREMENTS
+
+A Contributor may choose to distribute the Program in object code form under its
+own license agreement, provided that:
+
+  a) it complies with the terms and conditions of this Agreement; and
+  b) its license agreement:
+      i) effectively disclaims on behalf of all Contributors all warranties and
+         conditions, express and implied, including warranties or conditions of
+         title and non-infringement, and implied warranties or conditions of
+         merchantability and fitness for a particular purpose;
+     ii) effectively excludes on behalf of all Contributors all liability for
+         damages, including direct, indirect, special, incidental and
+         consequential damages, such as lost profits;
+    iii) states that any provisions which differ from this Agreement are offered
+         by that Contributor alone and not by any other party; and
+     iv) states that source code for the Program is available from such
+         Contributor, and informs licensees how to obtain it in a reasonable
+         manner on or through a medium customarily used for software exchange.
+
+When the Program is made available in source code form:
+
+  a) it must be made available under this Agreement; and
+  b) a copy of this Agreement must be included with each copy of the Program.
+     Contributors may not remove or alter any copyright notices contained within
+     the Program.
+
+Each Contributor must identify itself as the originator of its Contribution, if
+any, in a manner that reasonably allows subsequent Recipients to identify the
+originator of the Contribution.
+
+4. COMMERCIAL DISTRIBUTION
+
+Commercial distributors of software may accept certain responsibilities with
+respect to end users, business partners and the like. While this license is
+intended to facilitate the commercial use of the Program, the Contributor who
+includes the Program in a commercial product offering should do so in a manner
+which does not create potential liability for other Contributors. Therefore, if
+a Contributor includes the Program in a commercial product offering, such
+Contributor ("Commercial Contributor") hereby agrees to defend and indemnify
+every other Contributor ("Indemnified Contributor") against any losses, damages
+and costs (collectively "Losses") arising from claims, lawsuits and other legal
+actions brought by a third party against the Indemnified Contributor to the
+extent caused by the acts or omissions of such Commercial Contributor in
+connection with its distribution of the Program in a commercial product
+offering. The obligations in this section do not apply to any claims or Losses
+relating to any actual or alleged intellectual property infringement. In order
+to qualify, an Indemnified Contributor must: a) promptly notify the Commercial
+Contributor in writing of such claim, and b) allow the Commercial Contributor to
+control, and cooperate with the Commercial Contributor in, the defense and any
+related settlement negotiations. The Indemnified Contributor may participate in
+any such claim at its own expense.
+
+For example, a Contributor might include the Program in a commercial product
+offering, Product X. That Contributor is then a Commercial Contributor. If that
+Commercial Contributor then makes performance claims, or offers warranties
+related to Product X, those performance claims and warranties are such
+Commercial Contributor's responsibility alone. Under this section, the
+Commercial Contributor would have to defend claims against the other
+Contributors related to those performance claims and warranties, and if a court
+requires any other Contributor to pay any damages as a result, the Commercial
+Contributor must pay those damages.
+
+5. NO WARRANTY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
+IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each
+Recipient is solely responsible for determining the appropriateness of using and
+distributing the Program and assumes all risks associated with its exercise of
+rights under this Agreement , including but not limited to the risks and costs
+of program errors, compliance with applicable laws, damage to or loss of data,
+programs or equipment, and unavailability or interruption of operations.
+
+6. DISCLAIMER OF LIABILITY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
+CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST
+PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS
+GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+7. GENERAL
+
+If any provision of this Agreement is invalid or unenforceable under applicable
+law, it shall not affect the validity or enforceability of the remainder of the
+terms of this Agreement, and without further action by the parties hereto, such
+provision shall be reformed to the minimum extent necessary to make such
+provision valid and enforceable.
+
+If Recipient institutes patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Program itself
+(excluding combinations of the Program with other software or hardware)
+infringes such Recipient's patent(s), then such Recipient's rights granted under
+Section 2(b) shall terminate as of the date such litigation is filed.
+
+All Recipient's rights under this Agreement shall terminate if it fails to
+comply with any of the material terms or conditions of this Agreement and does
+not cure such failure in a reasonable period of time after becoming aware of
+such noncompliance. If all Recipient's rights under this Agreement terminate,
+Recipient agrees to cease use and distribution of the Program as soon as
+reasonably practicable. However, Recipient's obligations under this Agreement
+and any licenses granted by Recipient relating to the Program shall continue and
+survive.
+
+Everyone is permitted to copy and distribute copies of this Agreement, but in
+order to avoid inconsistency the Agreement is copyrighted and may only be
+modified in the following manner. The Agreement Steward reserves the right to
+publish new versions (including revisions) of this Agreement from time to time.
+No one other than the Agreement Steward has the right to modify this Agreement.
+The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation
+may assign the responsibility to serve as the Agreement Steward to a suitable
+separate entity. Each new version of the Agreement will be given a
+distinguishing version number. The Program (including Contributions) may always
+be distributed subject to the version of the Agreement under which it was
+received. In addition, after a new version of the Agreement is published,
+Contributor may elect to distribute the Program (including its Contributions)
+under the new version. Except as expressly stated in Sections 2(a) and 2(b)
+above, Recipient receives no rights or licenses to the intellectual property of
+any Contributor under this Agreement, whether expressly, by implication,
+estoppel or otherwise. All rights in the Program not expressly granted under
+this Agreement are reserved.
+
+This Agreement is governed by the laws of the State of New York and the
+intellectual property laws of the United States of America. No party to this
+Agreement will bring a legal action under this Agreement more than one year
+after the cause of action arose. Each party waives its rights to a jury trial in
+any resulting litigation.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2df3c05
--- /dev/null
+++ b/README.md
@@ -0,0 +1,150 @@
+# beckon
+
+A Clojure library to handle POSIX signals in JVM applications with style and
+grace. Sets up with the dirty parts and let you work with it in a (relatively)
+simple fashion.
+
+## Quick-start
+
+Add the following dependency to your `project.clj` file:
+
+```clj
+[beckon "0.1.1"]
+```
+
+Say you want to grab `SIGINT` and, say, print "Hahah, nothing can stop me!"
+whenever someone attempts to interrupt the process. Hit up your Emacs nREPL
+through `nrepl-jack-in` or similar methods, then write the following:
+
+```clj
+(require 'beckon)
+
+(let [print-function (fn [] (println "Hahah, nothing can stop me!"))]
+  (reset! (beckon/signal-atom "INT") #{print-function}))
+```
+
+That's it—it's not harder than that. To confirm whether this works or not, we
+can use the `raise!` function, which will raise a POSIX signal to the VM:
+
+```clj
+(beckon/raise! "INT")
+; prints nothing
+```
+
+So why didn't `raise!` print out anything? Whenever the JVM receives a signal,
+it decides to start up a new thread with maximum priority and do the signal
+handling asynchronously. As such, it will not show in the nrepl window. Move
+over to the `*nrepl-server*` buffer instead, and you'll see the message.
+
+By default, things like SIGTERM and SIGINT will terminate the running VM, so be
+a bit careful. We can of course play around with it in a REPL:
+
+```clj
+(beckon/raise! "TERM")
+; NB: This will terminate nREPL.
+```
+
+And if you somehow managed to screw up the signal handling and want to go back
+to the default, that's possible too:
+
+```clj
+(beckon/reinit! "INT")
+; Reinitializes the SIGINT signal handler.
+(beckon/raise! "INT")
+; NB: This will terminate your JVM process.
+```
+
+And well, that's really all you need to know in order to work with beckon.
+
+## Usage
+
+The core of beckon consist of 4 functions: `signal-atom`, `raise!`, `reinit!`
+and `reinit-all!`. `signal-atom` is the only one needed in production systems,
+usually. The other functions help out with debugging and resetting signal
+handling back to "factory settings"—the initial setup of signal handlers when
+the JVM starts up.
+
+### `signal-atom`
+
+`signal-atom` is the core piece of this library and (ab)uses atoms to setup
+signal handlers for Clojurians. As you'd guess, this returns an atom. The atom
+has a validator function attached to it, so only Seqable collections where every
+element are Runnable are legal values in this atom. All Clojure functions
+implements Runnable, but only functions which has a zero-argument invokation
+will actually work as a Runnable.
+
+Beckon require the contents of the atom to be a Seqable of Runnable because it
+makes it possible to add multiple independend signal handlers to a single
+signal. The signal handlers will always be executed sequentially, the only
+exception is if one of the functions throw an exception or error. If a function
+throws an exception, the signal handling will be cancelled (but no exception
+will be thrown), and if a function throws an error, the whole signal handling
+crashes. You can in theory "abuse" it to get conditional function dispatching.
+For example:
+
+```clj
+(reset! (beckon/signal-atom "INT")
+        [(fn [] (println "foo"))
+         (fn [] (println "bar") (throw (Exception.)))
+         (fn [] (println "We'll never see this"))])
+```
+
+Will only print `foo` and `bar`.
+
+It's not really a good way to do dispatching though, so you're advised to do
+this kind of logic within functions whenever possible.
+
+The signal handler is automatically updated whenever the atom is updated, but
+not vice versa. So if you use beckon, please don't try and hack in signal
+handling through another library or through the native java interface.
+
+### `raise!`
+
+`raise!` is probably the most understandable function in the system. It will
+send off a signal of the type given as input. For instance, `(beckon/raise!
+"INT")` will act as if a SIGINT signal was sent to the JVM process. It's handy
+to check out that your signal handlers work as intended.
+
+### `reinit!` and `reinit-all!`
+
+`reinit!` and `reinit-all!` are functions which reset the signal handlers back
+to their original state when the JVM was started. `reinit!` takes a single
+argument, the signal to reset, whereas `reinit-all!` takes zero and resets every
+single one.
+
+## How is a signal handled?
+
+In the JVM, whenever a signal is received, the VM starts up a new thread at
+`Thread.MAX_PRIORITY`, and executes it asynchronously. That's why we don't see
+any printing in nREPL, although it should work fine in command-line programs. I
+would recommend, however, to have some sort of logger or printer a signal
+handler sends a message to, instead of having the signal handler printing
+manually.
+
+## "FAQ"
+
+People using this library may get some issues when using it. If this list of
+common problems doesn't help you, please add a [new issue][new-issue] and we'll
+see what we can do about it!
+
+**Q:** My infinite sequence doesn't work with this library, why is that?  
+**A:** This library is designed to be easy to use for Clojure developers,
+  without sacrificing speed. As such, the collection of functions are realized
+  within Beckon and stored within a Java array. An infinite sequence doesn't fit
+  in a Java array, sadly.
+
+**Q:** For some reason, keywords, symbols and other things which are clearly not
+  functions are allowed in the collection of functions! Why is that?  
+**A:** Keywords, symbols and some persistent collections implement the `IFn`
+  interface in Clojure, which automatically means that they do in fact implement
+  Runnable. But, as mentioned, while they still implement Runnable, that won't
+  mean they are actually able to send back something of value. This is intended
+  to be fixed in a later version.
+
+[new-issue]: https://github.com/hyPiRion/beckon/issues/new "Add a new issue to Beckon"
+
+## License
+
+Copyright © 2013 Jean Niklas L'orange
+
+Distributed under the Eclipse Public License, the same as Clojure.
diff --git a/doc/intro.md b/doc/intro.md
new file mode 100644
index 0000000..5b3f915
--- /dev/null
+++ b/doc/intro.md
@@ -0,0 +1,3 @@
+# Introduction to beckon
+
+TODO: write [great documentation](http://jacobian.org/writing/great-documentation/what-to-write/)
diff --git a/project.clj b/project.clj
new file mode 100644
index 0000000..a31d7dd
--- /dev/null
+++ b/project.clj
@@ -0,0 +1,13 @@
+(defproject beckon "0.1.1"
+  :description "Handle POSIX signals in Clojure with style and grace."
+  :url "https://github.com/hyPiRion/beckon"
+  :license {:name "Eclipse Public License"
+            :url "http://www.eclipse.org/legal/epl-v10.html"}
+  :dependencies [[org.clojure/clojure "1.5.1"]]
+  :source-paths ["src/clojure"]
+  :java-source-paths ["src/java"]
+  :javac-options ["-target" "1.6" "-source" "1.6" "-Xlint:-options"]
+  :deploy-branches ["stable"]
+  :profiles {:dev {:plugins [[codox "0.6.4"]]
+                   :codox {:sources ["src/clojure"]
+                           :output-dir "codox"}}})
diff --git a/src/clojure/beckon.clj b/src/clojure/beckon.clj
new file mode 100644
index 0000000..6913d11
--- /dev/null
+++ b/src/clojure/beckon.clj
@@ -0,0 +1,45 @@
+(ns beckon
+  (:import (com.hypirion.beckon SignalAtoms SignalRegisterer)))
+
+(defn signal-atom
+  "Returns the beckon atom of the signal with the name signal-name. The changes
+  in the atom returned is reflected back to the signal handling, but NOT vice
+  versa. Multiple calls for the same signal atom will return the
+  same (identical) atom.
+
+  A beckon atom is an atom containing a Seqable Clojure collection, where all
+  the elements in the Seqable collection must be Runnable. Clojure functions are
+  by default Runnable if they can take zero arguments. By default would a beckon
+  atom be a Clojure Set with the default signal handler wrapped within a
+  Runnable. Note that with sets, the ordering of the Runnable is arbitrary; If
+  you need ordered calling, convert the set to a vector or list first.
+
+  The signal handling of a (modified) beckon atom is done by simply calling all
+  the functions in order. If the Runnable throws an Exception, the handling will
+  stop and no more elements will be called. If the signal handler throws an
+  Error, that Error will not be caught.
+
+  signal-name must be a legal POSIX signal, where SIG is omitted from the first
+  part of the name."
+  [signal-name]
+  (SignalAtoms/getSignalAtom signal-name))
+
+(defn raise!
+  "Raises a signal of the type specified. Consequently, the signal will also be
+  handled by the signal handling procedure.
+
+  signal-name must be a legal POSIX signal, where SIG is omitted from the first
+  part of the name."
+  [signal-name]
+  (SignalRegisterer/raiseSignal signal-name))
+
+(defn reinit!
+  "Reinitialises the signal handler to the signal name, just as it were at the
+  start of this JVM process."
+  [signal-name]
+  (SignalRegisterer/resetDefaultHandler signal-name))
+
+(defn reinit-all!
+  "Reintializes all signal handlers handled by beckon to their default values."
+  []
+  (SignalRegisterer/resetAllHandlers))
diff --git a/src/java/com/hypirion/beckon/SignalAtoms.java b/src/java/com/hypirion/beckon/SignalAtoms.java
new file mode 100644
index 0000000..19c7eae
--- /dev/null
+++ b/src/java/com/hypirion/beckon/SignalAtoms.java
@@ -0,0 +1,97 @@
+package com.hypirion.beckon;
+
+import java.util.Map;
+import java.util.HashMap;
+
+import clojure.lang.Atom;
+import clojure.lang.PersistentHashMap;
+import clojure.lang.Keyword;
+import clojure.lang.AFn;
+import clojure.lang.IFn;
+import clojure.lang.Seqable;
+import clojure.lang.ISeq;
+
+public class SignalAtoms {
+    private static final Map<String, Atom> atoms = new HashMap<String, Atom>();
+
+    /**
+     * The keyword <code>:signal</code>.
+     */
+    public static final Keyword SIGNAL = Keyword.intern("signal");
+
+    /**
+     * A standard Atom validator function which tests whether the new value is a
+     * Seqable, where each element in the Seqable implements Runnable.
+     */
+    public static final IFn SIGNAL_ATOM_VALIDATOR = new SignalAtomValidator();
+
+    /**
+     * Returns a Clojure Atom containing a Seqable. The Seqable represents a
+     * list of functions where the functions are called in order whenever a
+     * Signal by the type <code>signame</code> is received by this process.
+     *
+     * @exception SignalHandlerNotFoundException if this code is unable to
+     * detect a SignalHandler and/or a Signal class.
+     */
+    public static final synchronized Atom getSignalAtom(String signame)
+        throws SignalHandlerNotFoundException{
+        if (!atoms.containsKey(signame)) {
+            Object list;
+            try {
+                list = SignalRegistererHelper.getHandlerSeq(signame);
+            }
+            catch (LinkageError le) {
+                throw new SignalHandlerNotFoundException();
+            }
+            PersistentHashMap metadata = PersistentHashMap.create(SIGNAL, signame);
+            Atom atm = new Atom(list, metadata);
+            atm.setValidator(SIGNAL_ATOM_VALIDATOR);
+            SignalAtomWatch saw = new SignalAtomWatch(signame);
+            atm.addWatch(signame, saw);
+            atoms.put(signame, atm);
+        }
+        return atoms.get(signame);
+    }
+
+    private static class SignalAtomValidator extends AFn {
+        @Override
+        public Object invoke(Object newVal) {
+            if (newVal instanceof Seqable) {
+                ISeq seq = ((Seqable) newVal).seq();
+                // An empty seqable returns null.
+                if (seq == null) {
+                    return true;
+                }
+                int count = seq.count();
+                while (count --> 0) {
+                    Object o = seq.first();
+                    seq = seq.next();
+                    if (!(o instanceof Runnable)) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+            else if (newVal == null) {
+                return true;
+            }
+            else {
+                return false;
+            }
+        }
+    }
+
+    private static class SignalAtomWatch extends AFn {
+        public final String signame;
+
+        public SignalAtomWatch(String signame) {
+            this.signame = signame;
+        }
+
+        @Override
+        public Object invoke(Object key, Object ref, Object oldState, Object newState) {
+            SignalRegistererHelper.register(signame, (Seqable) newState);
+            return null;
+        }
+    }
+}
diff --git a/src/java/com/hypirion/beckon/SignalFolder.java b/src/java/com/hypirion/beckon/SignalFolder.java
new file mode 100644
index 0000000..fb43152
--- /dev/null
+++ b/src/java/com/hypirion/beckon/SignalFolder.java
@@ -0,0 +1,42 @@
+package com.hypirion.beckon;
+
+import sun.misc.Signal;
+import sun.misc.SignalHandler;
+
+import java.util.List;
+
+import clojure.lang.Seqable;
+import clojure.lang.ISeq;
+
+public class SignalFolder implements SignalHandler {
+    final Seqable originalList;
+    final private Runnable[] fns;
+
+    public SignalFolder(Seqable funs) {
+        ISeq seq = funs.seq();
+        // seq may be null
+        if (seq == null) {
+            fns = new Runnable[0];
+        }
+        else {
+            fns = new Runnable[seq.count()];
+            for (int i = 0; i < fns.length; i++) {
+                fns[i] = (Runnable) seq.first();
+                seq = seq.next();
+            }
+        }
+        originalList = funs;
+    }
+
+    public void handle(Signal sig) {
+        for (Runnable r : fns) {
+            boolean cont = true;
+            try {
+                r.run();
+            }
+            catch (Exception e) {
+                break;
+            }
+        }
+    }
+}
diff --git a/src/java/com/hypirion/beckon/SignalHandlerNotFoundException.java b/src/java/com/hypirion/beckon/SignalHandlerNotFoundException.java
new file mode 100644
index 0000000..b6d129b
--- /dev/null
+++ b/src/java/com/hypirion/beckon/SignalHandlerNotFoundException.java
@@ -0,0 +1,9 @@
+package com.hypirion.beckon;
+
+class SignalHandlerNotFoundException extends Exception {
+    public SignalHandlerNotFoundException() {
+        super("SignalHandler was not found on this JVM -- please report an" +
+              "issue at beckon's github page with which JVM and what version" +
+              "you're using.");
+    }
+}
diff --git a/src/java/com/hypirion/beckon/SignalRegisterer.java b/src/java/com/hypirion/beckon/SignalRegisterer.java
new file mode 100644
index 0000000..a941c92
--- /dev/null
+++ b/src/java/com/hypirion/beckon/SignalRegisterer.java
@@ -0,0 +1,55 @@
+package com.hypirion.beckon;
+
+public class SignalRegisterer {
+
+    /**
+     * Resets the default handler of the Signal with the name
+     * <code>signame</code> to its original value.
+     *
+     * @exception SignalHandlerNotFoundException if this code is unable to
+     * detect a SignalHandler and/or a Signal class.
+     */
+    public static void resetDefaultHandler(String signame)
+        throws SignalHandlerNotFoundException{
+        try {
+            SignalRegistererHelper.resetDefaultHandler(signame);
+        }
+        catch (LinkageError le) {
+            throw new SignalHandlerNotFoundException();
+        }
+    }
+
+    /**
+     * Resets all signal handlers to their original value.
+     *
+     * @exception SignalHandlerNotFoundException if this code is unable to
+     * detect a SignalHandler and/or a Signal class.
+     */
+    public static void resetAllHandlers()
+        throws SignalHandlerNotFoundException{
+        try {
+            SignalRegistererHelper.resetAll();
+        }
+        catch (LinkageError le) {
+            throw new SignalHandlerNotFoundException();
+        }
+    }
+
+    /**
+     * Raises a Signal with the name <code>signame</code> in the current
+     * process. Will consequently call the current SignalHandler for
+     * <code>signame</code> in another Thread with maximal priority.
+     *
+     * @exception SignalHandlerNotFoundException if this code is unable to
+     * detect a SignalHandler and/or a Signal class.
+     */
+    public static void raiseSignal(String signame)
+        throws SignalHandlerNotFoundException {
+        try {
+            SignalRegistererHelper.raise(signame);
+        }
+        catch (LinkageError le) {
+            throw new SignalHandlerNotFoundException();
+        }
+    }
+}
diff --git a/src/java/com/hypirion/beckon/SignalRegistererHelper.java b/src/java/com/hypirion/beckon/SignalRegistererHelper.java
new file mode 100644
index 0000000..730f61b
--- /dev/null
+++ b/src/java/com/hypirion/beckon/SignalRegistererHelper.java
@@ -0,0 +1,144 @@
+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);
+    }
+}
diff --git a/test/beckon/core_test.clj b/test/beckon/core_test.clj
new file mode 100644
index 0000000..7ad2ec2
--- /dev/null
+++ b/test/beckon/core_test.clj
@@ -0,0 +1,7 @@
+(ns beckon.core-test
+  (:require [clojure.test :refer :all]
+            [beckon.core :refer :all]))
+
+(deftest a-test
+  (testing "FIXME, I fail."
+    (is (= 0 1))))

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/beckon-clojure.git



More information about the pkg-java-commits mailing list