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); + } } /**