changeset 1618:ed75376cfe3a

2009-06-24 Omair Majid <omajid@redhat.com> * rt/net/sourceforge/jnlp/Launcher.java (launchApplication): Check for any existing single instance. Dont start a second instance. * rt/net/sourceforge/jnlp/resources/Messages.properties: Add RNoLockDir. * rt/net/sourceforge/jnlp/runtime/JNLPRuntime.java: Added USER, TMP_DIR, and changed LOCKS_DIR. * rt/net/sourceforge/jnlp/services/ExtendedSingleInstanceService.java New file. * rt/net/sourceforge/jnlp/services/InstanceExistsException.java: New file. * rt/net/sourceforge/jnlp/services/ServiceUtil.java (getSingleInstanceService): New function. (checkExistingSingleInstance): New function. * rt/net/sourceforge/jnlp/services/SingleInstanceLock.java: New file. * rt/net/sourceforge/jnlp/services/XServiceManagerStub.java: Add SingleInstanceService to serviceNames. Create a new instance of XSingleInstanceService as a privileged proxy. * rt/net/sourceforge/jnlp/services/XSingleInstanceService.java: New file.
author Omair Majid <omajid@redhat.com>
date Wed, 24 Jun 2009 15:02:45 -0400
parents 1ba29bfca5a0
children acf8090028c9
files ChangeLog rt/net/sourceforge/jnlp/Launcher.java rt/net/sourceforge/jnlp/resources/Messages.properties rt/net/sourceforge/jnlp/runtime/JNLPRuntime.java rt/net/sourceforge/jnlp/services/ExtendedSingleInstanceService.java rt/net/sourceforge/jnlp/services/InstanceExistsException.java rt/net/sourceforge/jnlp/services/ServiceUtil.java rt/net/sourceforge/jnlp/services/SingleInstanceLock.java rt/net/sourceforge/jnlp/services/XServiceManagerStub.java rt/net/sourceforge/jnlp/services/XSingleInstanceService.java
diffstat 10 files changed, 598 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Wed Jun 24 12:15:33 2009 -0400
+++ b/ChangeLog	Wed Jun 24 15:02:45 2009 -0400
@@ -1,3 +1,22 @@
+2009-06-24  Omair Majid  <omajid@redhat.com>
+
+	* rt/net/sourceforge/jnlp/Launcher.java (launchApplication): Check for any 
+	existing single instance.  Dont start a second instance.
+	* rt/net/sourceforge/jnlp/resources/Messages.properties: Add RNoLockDir.
+	* rt/net/sourceforge/jnlp/runtime/JNLPRuntime.java:
+	Added USER, TMP_DIR, and changed LOCKS_DIR.
+	* rt/net/sourceforge/jnlp/services/ExtendedSingleInstanceService.java
+	New file.
+	* rt/net/sourceforge/jnlp/services/InstanceExistsException.java: New file.
+	* rt/net/sourceforge/jnlp/services/ServiceUtil.java
+	(getSingleInstanceService): New function.
+	(checkExistingSingleInstance): New function.
+	* rt/net/sourceforge/jnlp/services/SingleInstanceLock.java: New file.
+	* rt/net/sourceforge/jnlp/services/XServiceManagerStub.java: Add
+	SingleInstanceService to serviceNames. Create a new instance of
+	XSingleInstanceService as a privileged proxy.
+	* rt/net/sourceforge/jnlp/services/XSingleInstanceService.java: New file. 
+
 2009-06-24  Omair Majid  <omajid@redhat.com>
 
 	* rt/net/sourceforge/jnlp/Parser.java: Add 1.5 and 6.0 to
--- a/rt/net/sourceforge/jnlp/Launcher.java	Wed Jun 24 12:15:33 2009 -0400
+++ b/rt/net/sourceforge/jnlp/Launcher.java	Wed Jun 24 15:02:45 2009 -0400
@@ -37,6 +37,8 @@
 import net.sourceforge.jnlp.runtime.ApplicationInstance;
 import net.sourceforge.jnlp.runtime.JNLPClassLoader;
 import net.sourceforge.jnlp.runtime.JNLPRuntime;
+import net.sourceforge.jnlp.services.InstanceExistsException;
+import net.sourceforge.jnlp.services.ServiceUtil;
 import net.sourceforge.jnlp.util.Reflect;
 
 /**
@@ -366,6 +368,12 @@
 
         try {
             
+            try {
+                ServiceUtil.checkExistingSingleInstance(file);
+            } catch (InstanceExistsException e) {
+                return null;
+            }
+            
             if (JNLPRuntime.getForksAllowed() && file.needsNewVM()) {
                 List<String> netxArguments = new LinkedList<String>();
                 netxArguments.add("-Xnofork");
--- a/rt/net/sourceforge/jnlp/resources/Messages.properties	Wed Jun 24 12:15:33 2009 -0400
+++ b/rt/net/sourceforge/jnlp/resources/Messages.properties	Wed Jun 24 15:02:45 2009 -0400
@@ -110,6 +110,7 @@
 RCantReplaceSM=Changing the SecurityManager is not allowed.
 RDenyStopped=Stopped applications have no permissions.
 RExitNoApp=Can not exit the JVM because the current application cannot be determined.
+RNoLockDir=Unable to create locks directory ({0})
 RUnexpected=Unexpected {0} at {1}
 
 # Boot options, message should be shorter than this ---------------->
--- a/rt/net/sourceforge/jnlp/runtime/JNLPRuntime.java	Wed Jun 24 12:15:33 2009 -0400
+++ b/rt/net/sourceforge/jnlp/runtime/JNLPRuntime.java	Wed Jun 24 15:02:45 2009 -0400
@@ -104,6 +104,9 @@
     /** contains the arguments passed to the jnlp runtime */
     private static List<String> initialArguments;
 
+    /** Username */
+    public static final String USER = System.getProperty("user.name");
+
     /** User's home directory */
     public static final String HOME_DIR = System.getProperty("user.home");
 
@@ -119,12 +122,17 @@
     /** the ~/.netx/security/trusted.certs file containing trusted certificates */
     public static final String CERTIFICATES_FILE = SECURITY_DIR + File.separator + "trusted.certs";
 
+    /** the /tmp/ directory used for temporary files */
+    public static final String TMP_DIR = System.getProperty("java.io.tmpdir");
+
     /**
-     * the ~/.netx/locks/ directory containing locks for single instance
+     * the /tmp/$USER/netx/locks/ directory containing locks for single instance
      * applications
      */
-    public static final String LOCKS_DIR = NETX_DIR + File.separator + "locks";
+    public static final String LOCKS_DIR = TMP_DIR + File.separator + USER + File.separator
+            + "netx" + File.separator + "locks";
 
+    /** the java.home directory */
     public static final String JAVA_HOME_DIR = System.getProperty("java.home");
     
     /** the JNLP file to open to display the network-based about window */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rt/net/sourceforge/jnlp/services/ExtendedSingleInstanceService.java	Wed Jun 24 15:02:45 2009 -0400
@@ -0,0 +1,49 @@
+// Copyright (C) 2009 Red Hat, Inc.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library 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
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+package net.sourceforge.jnlp.services;
+
+import javax.jnlp.SingleInstanceService;
+
+import net.sourceforge.jnlp.JNLPFile;
+
+/**
+ * Extends SingleInstanceService to provide a few additional methods that are
+ * required to initialize SingleInstanceService and check things. These methods
+ * are not exposed publicly
+ * 
+ * @author <a href="mailto:omajid@redhat.com">Omair Majid</a>
+ * 
+ */
+interface ExtendedSingleInstanceService extends SingleInstanceService {
+
+    /**
+     * Check if the instance identified by this jnlp file is already running
+     * 
+     * @param jnlpFile The JNLPFile that specifies the application
+     * 
+     * @throws InstanceExistsException if an instance of this application
+     *         already exists
+     * 
+     */
+    void checkSingleInstanceRunning(JNLPFile jnlpFile);
+
+    /**
+     * Start a single instance service based on the current application
+     */
+    void initializeSingleInstance();
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rt/net/sourceforge/jnlp/services/InstanceExistsException.java	Wed Jun 24 15:02:45 2009 -0400
@@ -0,0 +1,35 @@
+// Copyright (C) 2009 Red Hat, Inc.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library 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
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+package net.sourceforge.jnlp.services;
+
+/**
+ * 
+ * This class represents an exception indicating that an application instance
+ * already exists for this jnlp file
+ * 
+ * @author <a href="mailto:omajid@redhat.com">Omair Majid</a>
+ * 
+ */
+public class InstanceExistsException extends RuntimeException {
+
+    private static final long serialVersionUID = 7950552292795498272L;
+
+    public InstanceExistsException(String message) {
+        super(message);
+    }
+
+}
--- a/rt/net/sourceforge/jnlp/services/ServiceUtil.java	Wed Jun 24 12:15:33 2009 -0400
+++ b/rt/net/sourceforge/jnlp/services/ServiceUtil.java	Wed Jun 24 15:02:45 2009 -0400
@@ -17,12 +17,29 @@
 
 package net.sourceforge.jnlp.services;
 
-import java.lang.reflect.*;
-import java.security.*;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
 
-import javax.jnlp.*;
+import javax.jnlp.BasicService;
+import javax.jnlp.ClipboardService;
+import javax.jnlp.DownloadService;
+import javax.jnlp.ExtensionInstallerService;
+import javax.jnlp.FileOpenService;
+import javax.jnlp.FileSaveService;
+import javax.jnlp.PersistenceService;
+import javax.jnlp.PrintService;
+import javax.jnlp.ServiceManager;
+import javax.jnlp.SingleInstanceService;
+import javax.jnlp.UnavailableServiceException;
 
-import net.sourceforge.jnlp.runtime.*;
+import net.sourceforge.jnlp.JNLPFile;
+import net.sourceforge.jnlp.runtime.ApplicationInstance;
+import net.sourceforge.jnlp.runtime.JNLPRuntime;
 import net.sourceforge.jnlp.security.SecurityWarningDialog;
 
 /**
@@ -111,6 +128,25 @@
     }
 
     /**
+     * Returns the SingleInstanceService reference, or null if the service is
+     * unavailable.
+     */
+    public static SingleInstanceService getSingleInstanceService() {
+        return (SingleInstanceService) getService("javax.jnlp.SingleInstanceService");
+    }
+    
+    /**
+     * Checks that this application (represented by the jnlp) isnt already running
+     * @param jnlpFile the {@link JNLPFile} that specifies the application
+     * 
+     * @throws InstanceExistsException if an instance of this application already exists
+     */
+    public static void checkExistingSingleInstance(JNLPFile jnlpFile) {
+        ExtendedSingleInstanceService esis = (ExtendedSingleInstanceService) getSingleInstanceService();
+        esis.checkSingleInstanceRunning(jnlpFile);
+    }
+    
+    /**
      * Returns the service, or null instead of an UnavailableServiceException
      */
     private static Object getService(String name) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rt/net/sourceforge/jnlp/services/SingleInstanceLock.java	Wed Jun 24 15:02:45 2009 -0400
@@ -0,0 +1,203 @@
+// Copyright (C) 2009 Red Hat, Inc.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library 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
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+package net.sourceforge.jnlp.services;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.net.BindException;
+import java.net.ServerSocket;
+
+import net.sourceforge.jnlp.JNLPFile;
+import net.sourceforge.jnlp.runtime.JNLPRuntime;
+
+/**
+ * This class represents a Lock for single instance jnlp applications
+ * 
+ * The lock is per-session, per user.
+ * 
+ * @author <a href="mailto:omajid@redhat.com">Omair Majid</a>
+ */
+class SingleInstanceLock {
+
+    JNLPFile jnlpFile;
+    File lockFile = null;
+
+    public static final int INVALID_PORT = Integer.MIN_VALUE;
+
+    int port = INVALID_PORT;
+
+    /**
+     * Create an object to manage the instance lock for the specified JNLP file.
+     * 
+     * @param jnlpFile the jnlpfile to create the lock for
+     */
+    public SingleInstanceLock(JNLPFile jnlpFile) {
+        this.jnlpFile = jnlpFile;
+        lockFile = getLockFile();
+
+    }
+
+    /**
+     * Create/overwrite the instance lock for the jnlp file.
+     * 
+     * @param localPort the network port for the lock
+     * @throws IOException on any io problems
+     */
+    public void createWithPort(int localPort) throws IOException {
+
+        BufferedWriter lockFileWriter = new BufferedWriter(new FileWriter(lockFile, false));
+        lockFileWriter.write(String.valueOf(localPort));
+        lockFileWriter.newLine();
+        lockFileWriter.flush();
+        lockFileWriter.close();
+
+    }
+
+    /**
+     * Returns true if the lock if valid. That is, the lock exists, and port it
+     * points to is listening for incoming messages.
+     */
+    public boolean isValid() {
+        return (exists() && getPort() != INVALID_PORT && !isPortFree(getPort()));
+    }
+
+    /**
+     * Returns the port in this lock file.
+     */
+    public int getPort() {
+        if (!exists()) {
+            return INVALID_PORT;
+        }
+
+        try {
+            parseFile();
+        } catch (NumberFormatException e) {
+            port = INVALID_PORT;
+        } catch (IOException e) {
+            port = INVALID_PORT;
+        }
+        return port;
+
+    }
+
+    /**
+     * Returns true if the lock file already exists.
+     */
+    private boolean exists() {
+        return lockFile.exists();
+    }
+
+    /**
+     * Returns true if the port is free.
+     */
+    private boolean isPortFree(int port) {
+        try {
+            ServerSocket socket = new ServerSocket(port);
+            socket.close();
+            return true;
+        } catch (BindException e) {
+            return false;
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Return a file object that represents the lock file. The lock file itself
+     * may or may not exist.
+     */
+    private File getLockFile() {
+        File baseDir = new File(JNLPRuntime.LOCKS_DIR);
+
+        if (!baseDir.isDirectory() && !baseDir.mkdirs()) {
+            throw new RuntimeException(R("RNoLockDir", baseDir));
+        }
+        String lockFileName = getLockFileName();
+        File applicationLockFile = new File(baseDir, lockFileName);
+        return applicationLockFile;
+    }
+
+    /**
+     * Returns the name of the lock file.
+     */
+    private String getLockFileName() {
+        String initialName = "";
+
+        if (jnlpFile.getSourceLocation() != null) {
+            initialName = initialName + jnlpFile.getSourceLocation();
+        } else {
+            initialName = initialName + jnlpFile.getFileLocation();
+        }
+
+        if (jnlpFile.getFileVersion() != null) {
+            initialName = initialName + jnlpFile.getFileVersion().toString();
+        }
+        
+        initialName = initialName + getCurrentDisplay();
+        String encodedName;
+
+        /*
+         * FIXME
+         * 
+         * Assuming safe characters are 'a-z','A-Z','0-9', '_', '.'
+         */
+
+        encodedName = initialName.replaceAll("[^a-zA-Z0-9.]", "_");
+
+        return encodedName;
+
+    }
+
+    /**
+     * Parse the lock file.
+     * 
+     * @throws NumberFormatException
+     * @throws IOException
+     */
+    private void parseFile() throws NumberFormatException, IOException {
+        BufferedReader lockFileReader = new BufferedReader(new FileReader(lockFile));
+        int port = Integer.valueOf(lockFileReader.readLine());
+        lockFileReader.close();
+        this.port = port;
+    }
+
+    /**
+     * Returns a string identifying this display.
+     * 
+     * Implementation note: On systems with X support, this is the DISPLAY
+     * variable
+     * 
+     * @return a string that is guaranteed to be not null.
+     */
+    private String getCurrentDisplay() {
+        String display = System.getenv("DISPLAY");
+        return (display == null) ? "" : display;
+    }
+
+    private static String R(String key) {
+        return JNLPRuntime.getMessage(key);
+    }
+
+    private static String R(String key, Object param) {
+        return JNLPRuntime.getMessage(key, new Object[] { param });
+    }
+
+}
--- a/rt/net/sourceforge/jnlp/services/XServiceManagerStub.java	Wed Jun 24 12:15:33 2009 -0400
+++ b/rt/net/sourceforge/jnlp/services/XServiceManagerStub.java	Wed Jun 24 15:02:45 2009 -0400
@@ -51,7 +51,8 @@
         "javax.jnlp.FileOpenService",
         "javax.jnlp.FileSaveService",
         "javax.jnlp.ClipboardService",
-        "javax.jnlp.PrintService"
+        "javax.jnlp.PrintService",
+        "javax.jnlp.SingleInstanceService"
     };
 
     private static Object services[] = {
@@ -63,7 +64,8 @@
         ServiceUtil.createPrivilegedProxy(FileOpenService.class, new XFileOpenService()),
         ServiceUtil.createPrivilegedProxy(FileSaveService.class, new XFileSaveService()),
         ServiceUtil.createPrivilegedProxy(ClipboardService.class, new XClipboardService()),
-        ServiceUtil.createPrivilegedProxy(PrintService.class, new XPrintService())
+        ServiceUtil.createPrivilegedProxy(PrintService.class, new XPrintService()),
+        ServiceUtil.createPrivilegedProxy(ExtendedSingleInstanceService.class, new XSingleInstanceService())
     };
 
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rt/net/sourceforge/jnlp/services/XSingleInstanceService.java	Wed Jun 24 15:02:45 2009 -0400
@@ -0,0 +1,229 @@
+// Copyright (C) 2009 Red Hat, Inc.
+// 
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+// 
+// This library 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
+// Lesser General Public License for more details.
+// 
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+package net.sourceforge.jnlp.services;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.jnlp.SingleInstanceListener;
+import javax.management.InstanceAlreadyExistsException;
+
+import net.sourceforge.jnlp.JNLPFile;
+import net.sourceforge.jnlp.runtime.JNLPRuntime;
+
+/**
+ * This class implements SingleInstanceService
+ * 
+ * @author <a href="mailto:omajid@redhat.com">Omair Majid</a>
+ */
+public class XSingleInstanceService implements ExtendedSingleInstanceService {
+
+    boolean initialized = false;
+    List<SingleInstanceListener> listeners = new LinkedList<SingleInstanceListener>();
+
+    /**
+     * Implements a server that listens for arguments from new instances of this
+     * application
+     * 
+     */
+    class SingleInstanceServer implements Runnable {
+
+        SingleInstanceLock lockFile = null;
+
+        public SingleInstanceServer(SingleInstanceLock lockFile) {
+            this.lockFile = lockFile;
+        }
+
+        public void run() {
+            ServerSocket listeningSocket = null;
+            try {
+                listeningSocket = new ServerSocket(0);
+                lockFile.createWithPort(listeningSocket.getLocalPort());
+
+                if (JNLPRuntime.isDebug()) {
+                    System.out.println("Starting SingleInstanceServer on port" + listeningSocket);
+                }
+
+                while (true) {
+                    try {
+                        Socket communicationSocket = listeningSocket.accept();
+                        ObjectInputStream ois = new ObjectInputStream(communicationSocket
+                                .getInputStream());
+                        String[] arguments = (String[]) ois.readObject();
+                        notifySingleInstanceListeners(arguments);
+                    } catch (Exception exception) {
+                        // not much to do here...
+                        exception.printStackTrace();
+                    }
+
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            } finally {
+                if (listeningSocket != null) {
+                    try {
+                        listeningSocket.close();
+                    } catch (IOException e) {
+                        // Give up.
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Create a new XSingleInstanceService
+     */
+    protected XSingleInstanceService() {
+    }
+
+    /**
+     * Initialize the new SingleInstanceService
+     * 
+     * @throws InstanceAlreadyExistsException if the instance already exists
+     */
+    public void initializeSingleInstance() {
+        if (!initialized) {
+            // this is called after the application has started. so safe to use
+            // JNLPRuntime.getApplication()
+            checkSingleInstanceRunning(JNLPRuntime.getApplication().getJNLPFile());
+            initialized = true;
+            SingleInstanceLock lockFile;
+            JNLPFile jnlpFile = JNLPRuntime.getApplication().getJNLPFile();
+            lockFile = new SingleInstanceLock(jnlpFile);
+            if (!lockFile.isValid()) {
+                startListeningServer(lockFile);
+            }
+        }
+
+    }
+
+    /**
+     * Check if another instance of this application is already running
+     * 
+     * @param jnlpFile The {@link JNLPFile} that specifies the application
+     * 
+     * @throws InstanceExistsException if an instance of this application
+     *         already exists
+     */
+    public void checkSingleInstanceRunning(JNLPFile jnlpFile) {
+        SingleInstanceLock lockFile = new SingleInstanceLock(jnlpFile);
+        if (lockFile.isValid()) {
+            int port = lockFile.getPort();
+            if (JNLPRuntime.isDebug()) {
+                System.out.println("Lock file is valid (port=" + port + "). Exiting.");
+            }
+            try {
+                sendProgramArgumentsToExistingApplication(port, jnlpFile.getApplication()
+                        .getArguments());
+                throw new InstanceExistsException(String.valueOf(port));
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    /**
+     * Start the listening server to accept arguments from new isntances of
+     * applications
+     * 
+     * @param lockFile
+     */
+    private void startListeningServer(SingleInstanceLock lockFile) {
+        SingleInstanceServer server = new SingleInstanceServer(lockFile);
+        new Thread(server).start();
+    }
+
+    /**
+     * Send the arguments for this application to the main instance
+     * 
+     * @param port the port at which the SingleInstanceServer is listening at
+     * @param arguments the new arguments
+     * @throws IOException on any io exception
+     */
+    private void sendProgramArgumentsToExistingApplication(int port, String[] arguments)
+            throws IOException {
+        try {
+            Socket serverCommunicationSocket = new Socket((String) null, port);
+            ObjectOutputStream argumentStream = new ObjectOutputStream(serverCommunicationSocket
+                    .getOutputStream());
+            argumentStream.writeObject(arguments);
+            argumentStream.close();
+            serverCommunicationSocket.close();
+
+        } catch (UnknownHostException unknownHost) {
+            if (JNLPRuntime.isDebug()) {
+                System.out.println("Unable to find localhost");
+            }
+            throw new RuntimeException(unknownHost);
+        }
+    }
+
+    /**
+     * Notify any SingleInstanceListener with new arguments
+     * 
+     * @param arguments the new arguments to the application
+     */
+    private void notifySingleInstanceListeners(String[] arguments) {
+        for (SingleInstanceListener listener : listeners) {
+            // TODO this proxy is privileged. should i worry about security in
+            // methods being called?
+            listener.newActivation(arguments);
+        }
+    }
+
+    /**
+     * Add the specified SingleInstanceListener
+     * 
+     * @throws InstanceExistsException, which is likely to terminate the
+     *         application but not guaranteed to
+     */
+    public void addSingleInstanceListener(SingleInstanceListener sil) {
+        initializeSingleInstance();
+
+        if (sil == null) {
+            return;
+        }
+
+        listeners.add(sil);
+    }
+
+    /**
+     * Remove the specified SingleInstanceListener
+     * 
+     * @throws InstanceExistsException if an instance of this single instance
+     *         application already exists
+     * 
+     */
+    public void removeSingleInstanceListener(SingleInstanceListener sil) {
+        initializeSingleInstance();
+
+        if (sil == null) {
+            return;
+        }
+
+        listeners.remove(sil);
+    }
+
+}