diff --git a/src/main/java/org/kohsuke/file_leak_detector/ActivityListener.java b/src/main/java/org/kohsuke/file_leak_detector/ActivityListener.java
new file mode 100644
index 0000000..40bf2d1
--- /dev/null
+++ b/src/main/java/org/kohsuke/file_leak_detector/ActivityListener.java
@@ -0,0 +1,61 @@
+package org.kohsuke.file_leak_detector;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.RandomAccessFile;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.nio.channels.SocketChannel;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.zip.ZipFile;
+
+/**
+ * Allows user programs to receive callbacks for file open/close activities.
+ *
+ *
+ * Instantiate this class and put it into {@link #LIST} to start receiving callbacks.
+ * Listeners must be concurrent and re-entrant safe.
+ *
+ * @author Michal Linhard (michal@linhard.sk)
+ * @author Kohsuke Kawaguchi
+ */
+public abstract class ActivityListener {
+ /**
+ * Called when a new file is opened.
+ *
+ * @param obj
+ * {@link FileInputStream}, {@link FileOutputStream}, {@link RandomAccessFile}, or {@link ZipFile}.
+ * @param file
+ * File being opened.
+ */
+ public void open(Object obj, File file) {
+ }
+
+ /**
+ * Called when a new socket is opened.
+ *
+ * @param obj
+ * {@link Socket}, {@link ServerSocket} or {@link SocketChannel}
+ */
+ public void openSocket(Object obj) {
+ }
+
+ /**
+ * Called when a file is closed.
+ *
+ * This method tolerates a double-close where a close method is called on an already closed object.
+ *
+ * @param obj
+ * {@link FileInputStream}, {@link FileOutputStream}, {@link RandomAccessFile}, {@link Socket}, {@link ServerSocket}, or {@link ZipFile}.
+ */
+ public void close(Object obj) {
+ }
+
+ /**
+ * These listeners get called.
+ */
+ public static final List LIST = new CopyOnWriteArrayList();
+
+}
diff --git a/src/main/java/org/kohsuke/file_leak_detector/AgentMain.java b/src/main/java/org/kohsuke/file_leak_detector/AgentMain.java
index f0cc619..72caf7c 100644
--- a/src/main/java/org/kohsuke/file_leak_detector/AgentMain.java
+++ b/src/main/java/org/kohsuke/file_leak_detector/AgentMain.java
@@ -72,7 +72,7 @@ public static void premain(String agentArguments, Instrumentation instrumentatio
Listener.ERROR = new PrintWriter(new FileOutputStream(t.substring(6)));
} else
if(t.startsWith("listener=")) {
- Listener.EXTERNAL = (ExternalListener) AgentMain.class.getClassLoader().loadClass(t.substring(9)).newInstance();
+ ActivityListener.LIST.add((ActivityListener) AgentMain.class.getClassLoader().loadClass(t.substring(9)).newInstance());
} else {
System.err.println("Unknown option: "+t);
usageAndQuit();
@@ -154,6 +154,7 @@ static void printOptions() {
System.err.println(" http=PORT - Run a mini HTTP server that you can access to get stats on demand");
System.err.println(" Specify 0 to choose random available port, -1 to disable, which is default.");
System.err.println(" strong - Don't let GC auto-close leaking file descriptors");
+ System.err.println(" listener=S - Specify the fully qualified name of ActivityListener class to activate from beginning");
}
static List createSpec() {
diff --git a/src/main/java/org/kohsuke/file_leak_detector/ExternalListener.java b/src/main/java/org/kohsuke/file_leak_detector/ExternalListener.java
deleted file mode 100644
index 34a1321..0000000
--- a/src/main/java/org/kohsuke/file_leak_detector/ExternalListener.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.kohsuke.file_leak_detector;
-
-import java.io.File;
-
-/**
- *
- * Allows external listeners for the open file and open socket events.
- *
- * @author Michal Linhard (michal@linhard.sk)
- */
-public interface ExternalListener {
- /**
- *
- * Called when a new file is opened.
- *
- * @param obj
- * @param file
- */
- void open(Object obj, File file);
-
- /**
- *
- * Called when a new socket is opened.
- *
- * @param obj
- */
- void openSocket(Object obj);
-}
diff --git a/src/main/java/org/kohsuke/file_leak_detector/Listener.java b/src/main/java/org/kohsuke/file_leak_detector/Listener.java
index 2288b93..53a41dc 100644
--- a/src/main/java/org/kohsuke/file_leak_detector/Listener.java
+++ b/src/main/java/org/kohsuke/file_leak_detector/Listener.java
@@ -175,11 +175,6 @@ public void dump(String prefix, PrintWriter ps) {
*/
/*package*/ static boolean AGENT_INSTALLED = false;
- /**
- * Implementation of {@link ExternalListener}.
- */
- public static ExternalListener EXTERNAL = null;
-
/**
* Returns true if the leak detector agent is running.
*/
@@ -200,34 +195,46 @@ public static synchronized void makeStrong() {
* File being opened.
*/
public static synchronized void open(Object _this, File f) {
- if (EXTERNAL != null)
- EXTERNAL.open(_this, f);
put(_this, new FileRecord(f));
+
+ for (ActivityListener al : ActivityListener.LIST) {
+ al.open(_this,f);
+ }
}
/**
* Called when a socket is opened.
*/
public static synchronized void openSocket(Object _this) {
- if (EXTERNAL != null)
- EXTERNAL.openSocket(_this);
// intercept when
if (_this instanceof SocketImpl) {
try {
// one of the following must be true
SocketImpl si = (SocketImpl) _this;
Socket s = (Socket)SOCKETIMPL_SOCKET.get(si);
- if (s!=null)
+ if (s!=null) {
put(_this, new SocketRecord(s));
+ for (ActivityListener al : ActivityListener.LIST) {
+ al.openSocket(s);
+ }
+ }
ServerSocket ss = (ServerSocket)SOCKETIMPL_SERVER_SOCKET.get(si);
- if (ss!=null)
+ if (ss!=null) {
put(_this, new ServerSocketRecord(ss));
+ for (ActivityListener al : ActivityListener.LIST) {
+ al.openSocket(ss);
+ }
+ }
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
}
if (_this instanceof SocketChannel) {
put(_this, new SocketChannelRecord((SocketChannel) _this));
+
+ for (ActivityListener al : ActivityListener.LIST) {
+ al.openSocket(_this);
+ }
}
}
@@ -263,6 +270,9 @@ public static synchronized void close(Object _this) {
r.dump("Closed ",TRACE);
tracing = false;
}
+ for (ActivityListener al : ActivityListener.LIST) {
+ al.close(_this);
+ }
}
/**