/**
   * Make a new connection.
   *
   * @return
   * @throws IllegalStateException - Thrown if connection cannot be created.
   * @since 1.0.0
   */
  protected IREMConnection createConnection() throws IllegalStateException {
    // If we have a server port, then the server is probably open. If we don't then there is no
    // server.
    if (fServerPort != 0) {
      // We are putting it off into a thread because there are no timeout capabilities on getting a
      // socket.
      // So we need to allow for that.
      final Socket[] scArray = new Socket[1];
      final boolean[] waiting = new boolean[] {true};
      Thread doIt =
          new Thread(
              new Runnable() {
                public void run() {
                  try {
                    Socket sc = new Socket("localhost", fServerPort); // $NON-NLS-1$
                    synchronized (this) {
                      if (waiting[0]) scArray[0] = sc;
                      else
                        sc
                            .close(); // We are no longer waiting on this thread so close the socket
                                      // since no one will use it.
                    }
                  } catch (IOException e) {
                    ProxyPlugin.getPlugin()
                        .getLogger()
                        .log(
                            new Status(
                                IStatus.WARNING,
                                ProxyPlugin.getPlugin().getBundle().getSymbolicName(),
                                0,
                                "",
                                e)); //$NON-NLS-1$
                  }
                }
              });

      doIt.start();
      while (true) {
        try {
          doIt.join(!fNoTimeouts ? 60000 : 0);
          synchronized (doIt) {
            waiting[0] = false; // To let it know we are no longer waiting
          }
          break;
        } catch (InterruptedException e) {
        }
      }

      if (scArray[0] == null) {
        // Log where we are at so we can know where it was we down.
        ProxyPlugin.getPlugin()
            .getLogger()
            .log(
                new Status(
                    IStatus.WARNING,
                    ProxyPlugin.getPlugin().getBundle().getSymbolicName(),
                    0,
                    "",
                    new IllegalStateException(
                        ProxyRemoteMessages
                            .REMProxyFactoryRegistry_ConnectionCreationFailed_INFO_))); //$NON-NLS-1$
        throw new IllegalStateException(
            ProxyRemoteMessages
                .REMProxyFactoryRegistry_CouldNotCreateSocketConnectionToRemoteVM_EXC_); // Couldn't
                                                                                         // get one,
                                                                                         // probably
                                                                                         // server
                                                                                         // is down.
                                                                                         // //$NON-NLS-1$
      }

      REMConnection connection = new REMConnection(scArray[0], fNoTimeouts);
      if (connection.isConnected()) return connection;

      // Failed, close the socket.
      try {
        scArray[0].close();
      } catch (IOException e) {
      }
    } else
      ProxyPlugin.getPlugin()
          .getLogger()
          .log(
              new Status(
                  IStatus.WARNING,
                  ProxyPlugin.getPlugin().getBundle().getSymbolicName(),
                  0,
                  "No Server to retrieve a connection.",
                  null)); ///$NON-NLS-1$

    throw new IllegalStateException(
        ProxyRemoteMessages.REMProxyFactoryRegistry_CouldNotCreateSocketConnectionToRemoteVM_EXC_);
  }
/**
 * This is the factory registry to use for Remote VM. It adds to the standard registry, connection
 * specific information.
 *
 * <p>This will always hold onto one connection open until termination is requested. That way while
 * the IDE is up, the remove vm won't time out.
 */
public class REMProxyFactoryRegistry extends BaseProxyFactoryRegistry {

  public static final String REMOTE_REGISTRY_TYPE_ID = "org.eclipse.jem.REMOTE"; // $NON-NLS-1$

  protected int fServerPort = 0; // The server port to use when making connections.
  protected REMCallbackRegistry fCallbackServer; // The callback server thread for this remote vm.
  protected Stack fConnectionPool = new Stack(); // Stack of free connections.
  protected static int NUMBER_FREE_CONNECTIONS = 5; // Number of free connections to keep open.
  protected IProcess
      fProcess; // The process that is the server. If null and fServerPort is not zero,
  // then this registry is in test mode
  // and the server is in same the process.
  protected String fName;
  protected int fCallbackServerPort;
  protected Integer fRegistryKey;
  protected REMRegistryController fRegistryController;

  protected static final Object TERMINATE_JOB_FAMILY = new Object();

  // Package protected because only the ProxyVMStarter should set this flag. It would set it if
  // working with a debugger because we don't how long it will be to respond to requests when
  // someone is working with a debugger.
  boolean fNoTimeouts = false;

  // This is set via the static setGlobalNoTimeouts() method, or via debug options flag. It is here
  // so that
  // when debugging callbacks, but not debugging remote vm, that no timeouts for any registry will
  // occur.
  // Or it can be set through the debug .options flag.
  static boolean fGlobalNoTimeouts =
      "true"
          .equalsIgnoreCase(
              Platform.getDebugOption(
                  ProxyPlugin.getPlugin().getBundle().getSymbolicName()
                      + ProxyRemoteUtil.NO_TIMEOUTS)); // $NON-NLS-1$;

  /**
   * Typicall set through the "expression" evaluation of the debugger.
   *
   * @param noTimeouts
   * @since 1.0.0
   */
  public static void setGlobalNoTimeouts(boolean noTimeouts) {
    fGlobalNoTimeouts = noTimeouts;
  }

  // An internal thread that locks and waits for the remote vm to register itself.
  private WaitForRegistrationThread waitRegistrationThread;

  private class WaitForRegistrationThread extends Thread {
    public WaitForRegistrationThread() {
      super("Wait for remote vm registration thread"); // $NON-NLS-1$
    }

    /** @see java.lang.Thread#run() */
    public void run() {
      // Wait for registration. Put it into a thread so this
      // can occur while other stuff goes on. It locks the fConnectionPool
      // until done so that the first request for a connection by anyone
      // else will wait until this thread is finished.

      synchronized (fConnectionPool) {
        synchronized (REMProxyFactoryRegistry.this) {
          // Notify the main thread that we have the
          // connection pool locked.
          REMProxyFactoryRegistry.this.notifyAll();
        }
        synchronized (this) {
          // sync on self so that it can be notified when finally receive the registration
          long stopTime = System.currentTimeMillis() + 60000;
          while (waitRegistrationThread != null
              && (fNoTimeouts || System.currentTimeMillis() < stopTime)) {
            try {
              Thread.currentThread().wait(60000);
            } catch (InterruptedException e) {
            }
          }
        }
      }

      waitRegistrationThread = null; // No longer exists.			
    }
  }

  public REMProxyFactoryRegistry(REMRegistryController registryController, String name) {
    super(REMOTE_REGISTRY_TYPE_ID);
    fRegistryController = registryController;
    fRegistryKey =
        fRegistryController.registerRegistry(this); // Register the registry with the plugin.	
    fName = name;

    // Get the waitRegistrationThread started before we actually launch remote vm so
    // that it is waiting when the callback comes in.
    synchronized (this) {
      waitRegistrationThread = new WaitForRegistrationThread();
      waitRegistrationThread.start();

      // Now we will wait until the registration callback has been done. The thread will
      // signal us when that is done. This is so that we don't continue on and let
      // a work connection be requested before we even got a chance to start waiting
      // for the registration.
      while (true) {
        try {
          wait();
          break;
        } catch (InterruptedException e) {
        }
      }
      ;
    }
  }

  public Integer getRegistryKey() {
    return fRegistryKey;
  }

  public void initializeRegistry(IProcess process) {
    fProcess = process;
    processListener =
        new IDebugEventSetListener() {
          /** @see org.eclipse.debug.core.IDebugEventSetListener#handleDebugEvents(DebugEvent[]) */
          public void handleDebugEvents(DebugEvent[] events) {
            for (int i = 0; i < events.length; i++) {
              DebugEvent e = events[i];
              if (e.getSource() == fProcess && e.getKind() == DebugEvent.TERMINATE) {
                // We terminating too soon. Pop up a msg.
                IStreamsProxy stProxy = fProcess.getStreamsProxy();
                java.io.StringWriter s = new java.io.StringWriter();
                java.io.PrintWriter w = new java.io.PrintWriter(s);

                String msg =
                    MessageFormat.format(
                        ProxyRemoteMessages.Proxy_Terminated_too_soon_ERROR_, new Object[] {fName});
                w.println(msg);
                w.println(ProxyRemoteMessages.VM_TERMINATED_INFO_);
                w.println(ProxyRemoteMessages.VM_COMMAND_LINE);
                w.println(fProcess.getAttribute(IProcess.ATTR_CMDLINE));
                w.println(ProxyRemoteMessages.VM_TERMINATED_LINE1);
                w.println(stProxy.getErrorStreamMonitor().getContents());
                w.println(ProxyRemoteMessages.VM_TERMINATED_LINE2);
                w.println(stProxy.getOutputStreamMonitor().getContents());
                w.println(ProxyRemoteMessages.VM_TERMINATED_LINE3);
                w.close();

                DebugModeHelper dh = new DebugModeHelper();
                dh.displayErrorMessage(ProxyRemoteMessages.Proxy_Error_Title, msg);
                ProxyPlugin.getPlugin()
                    .getLogger()
                    .log(
                        new Status(
                            IStatus.WARNING,
                            ProxyPlugin.getPlugin().getBundle().getSymbolicName(),
                            0,
                            s.toString(),
                            null));
                processListener = null;
                DebugPlugin.getDefault().removeDebugEventListener(this);
                terminateRegistry();
                break;
              }
            }
          }
        };

    DebugPlugin.getDefault().addDebugEventListener(processListener);
  }

  private IDebugEventSetListener processListener = null;

  /** Get the CallbackRegistry */
  public ICallbackRegistry getCallbackRegistry() {
    if (fCallbackServer == null) fCallbackServer = new REMCallbackRegistry(fName, this);
    return fCallbackServer;
  }

  /**
   * This is called by the registry controller to tell the registry to terminate with prejudice all
   * pending TerminateJobs.
   *
   * @since 1.1.0
   */
  public static void cancelAllTerminateJobs() {
    IJobManager jobManager = Job.getJobManager();
    jobManager.cancel(TERMINATE_JOB_FAMILY);
    try {
      jobManager.join(TERMINATE_JOB_FAMILY, null);
    } catch (OperationCanceledException e) {
    } catch (InterruptedException e) {
    }
  }

  private static class TerminateProcess extends Job {
    private IProcess process;

    public TerminateProcess(IProcess process) {
      super(ProxyRemoteMessages.REMProxyFactoryRegistry_Job_TerminateProcess_Title);
      this.process = process;
    }

    public boolean belongsTo(Object family) {
      return family == TERMINATE_JOB_FAMILY || super.belongsTo(family);
    }

    /* (non-Javadoc)
     * @see java.lang.Thread#run()
     */
    public IStatus run(IProgressMonitor mon) {
      try {
        // There is no join on a process available, so we will have to
        // busy wait. Give it 10 seconds in 1/10 second intervals.
        for (int i = 0; !process.isTerminated() && i < 100; i++) {
          try {
            Thread.sleep(100);
          } catch (InterruptedException e) {
          }
        }
        if (!process.isTerminated()) {
          process.terminate();
        }
      } catch (DebugException e) {
      }
      return Status.OK_STATUS;
    }
  }

  protected void registryTerminated(boolean wait) {
    if (processListener != null) {
      // Remove listener cause we are now going to terminate process and don't want premature
      // terminate notice.
      // Sometimes in shutdown we are called and the debug plugin may of already been shutdown. In
      // that case the db
      // will be null and there is nothing remove listener from.
      DebugPlugin db = DebugPlugin.getDefault();
      if (db != null) db.removeDebugEventListener(processListener);
      processListener = null;
    }

    Job tjob = null;
    if (waitRegistrationThread != null) {
      synchronized (waitRegistrationThread) {
        // Still waiting. close it out.
        WaitForRegistrationThread wThread = waitRegistrationThread;
        waitRegistrationThread = null;
        wThread.notifyAll();
      }
    }
    if (fServerPort != 0) {
      IREMConnection closeCon = null; // The connection we will use to close the remote vm.
      synchronized (fConnectionPool) {
        // Now we walk through all of the free connections and close them properly.
        Iterator itr = fConnectionPool.iterator();
        if (itr.hasNext()) closeCon = (IREMConnection) itr.next();
        while (itr.hasNext()) {
          IREMConnection con = (IREMConnection) itr.next();
          con.close();
        }
      }

      // Now we terminate the server.
      if (closeCon == null)
        try {
          closeCon =
              getFreeConnection(); // There weren't any free connections, so get a new one so that
                                   // we can close it.
        } catch (IllegalStateException e) {
          // Do nothing, don't want to stop termination just because we can't get a connection.
        }
      if (closeCon != null) {
        closeCon
            .terminateServer(); // We got a connection to terminate (process may of terminated
                                // early, so we would not have a conn then).
      }
      fConnectionPool.clear();
      fServerPort = 0;

      if (fProcess != null && !fRegistryController.inShutDown()) {
        tjob = new TerminateProcess(fProcess);
        tjob.setSystem(true);
        tjob.schedule();
        fProcess = null;
      }
    }

    if (fCallbackServer != null) {
      fCallbackServer.requestShutdown();
      fCallbackServer = null;
    }

    fConnectionPool.clear();
    fRegistryController.deregisterRegistry(fRegistryKey); // De-register this registry.

    if (wait && tjob != null) {
      try {
        tjob.join();
      } catch (InterruptedException e) {
        // It timed out, so we'll just go on.
      }
    }
  }

  /** Return the server port number. */
  public int getServerPort() {
    return fServerPort;
  }

  /*
   * set the server port.
   */
  void setServerPort(int serverport) {
    fServerPort = serverport;
    if (waitRegistrationThread != null) {
      synchronized (waitRegistrationThread) {
        // Close it out, we are now registered
        WaitForRegistrationThread wThread = waitRegistrationThread;
        waitRegistrationThread = null;
        wThread.notifyAll();
      }
    }
  }

  /**
   * Get a free connection
   *
   * @return
   * @throws IllegalStateException - Thrown if a connection cannot be created.
   * @since 1.0.0
   */
  public IREMConnection getFreeConnection() throws IllegalStateException {
    Thread thread = Thread.currentThread();
    if (thread instanceof REMCallbackThread) {
      // The current thread is a call back thread, so just reuse the connection.
      // But this thread could actually be trying to access another registry.
      // So if this thread is for this registry, use it, if not for this registry, create a new
      // connection.
      // But if for this registry AND is already in a transaction, we need a fresh connection.
      REMCallbackThread callbackThread = (REMCallbackThread) thread;
      if (callbackThread.registry == this && !callbackThread.inTransaction()) {
        // This way any calls out to the remote vm will be on same thread as callback caller
        // on remote vm because that thread is waiting on this connection for commands.
        IREMConnection c = (callbackThread).getConnection();
        if (c.isConnected()) return c;
        else
          throw new IllegalStateException(
              ProxyRemoteMessages.REMProxyFactoryRegistry_CallbackConnectionNotWorking_EXC_);
      }
    }
    synchronized (fConnectionPool) {
      if (!fConnectionPool.isEmpty()) return (IREMConnection) fConnectionPool.pop();
      // else we need to allocate one.
      return createConnection();
    }
  }

  /**
   * Make a new connection.
   *
   * @return
   * @throws IllegalStateException - Thrown if connection cannot be created.
   * @since 1.0.0
   */
  protected IREMConnection createConnection() throws IllegalStateException {
    // If we have a server port, then the server is probably open. If we don't then there is no
    // server.
    if (fServerPort != 0) {
      // We are putting it off into a thread because there are no timeout capabilities on getting a
      // socket.
      // So we need to allow for that.
      final Socket[] scArray = new Socket[1];
      final boolean[] waiting = new boolean[] {true};
      Thread doIt =
          new Thread(
              new Runnable() {
                public void run() {
                  try {
                    Socket sc = new Socket("localhost", fServerPort); // $NON-NLS-1$
                    synchronized (this) {
                      if (waiting[0]) scArray[0] = sc;
                      else
                        sc
                            .close(); // We are no longer waiting on this thread so close the socket
                                      // since no one will use it.
                    }
                  } catch (IOException e) {
                    ProxyPlugin.getPlugin()
                        .getLogger()
                        .log(
                            new Status(
                                IStatus.WARNING,
                                ProxyPlugin.getPlugin().getBundle().getSymbolicName(),
                                0,
                                "",
                                e)); //$NON-NLS-1$
                  }
                }
              });

      doIt.start();
      while (true) {
        try {
          doIt.join(!fNoTimeouts ? 60000 : 0);
          synchronized (doIt) {
            waiting[0] = false; // To let it know we are no longer waiting
          }
          break;
        } catch (InterruptedException e) {
        }
      }

      if (scArray[0] == null) {
        // Log where we are at so we can know where it was we down.
        ProxyPlugin.getPlugin()
            .getLogger()
            .log(
                new Status(
                    IStatus.WARNING,
                    ProxyPlugin.getPlugin().getBundle().getSymbolicName(),
                    0,
                    "",
                    new IllegalStateException(
                        ProxyRemoteMessages
                            .REMProxyFactoryRegistry_ConnectionCreationFailed_INFO_))); //$NON-NLS-1$
        throw new IllegalStateException(
            ProxyRemoteMessages
                .REMProxyFactoryRegistry_CouldNotCreateSocketConnectionToRemoteVM_EXC_); // Couldn't
                                                                                         // get one,
                                                                                         // probably
                                                                                         // server
                                                                                         // is down.
                                                                                         // //$NON-NLS-1$
      }

      REMConnection connection = new REMConnection(scArray[0], fNoTimeouts);
      if (connection.isConnected()) return connection;

      // Failed, close the socket.
      try {
        scArray[0].close();
      } catch (IOException e) {
      }
    } else
      ProxyPlugin.getPlugin()
          .getLogger()
          .log(
              new Status(
                  IStatus.WARNING,
                  ProxyPlugin.getPlugin().getBundle().getSymbolicName(),
                  0,
                  "No Server to retrieve a connection.",
                  null)); ///$NON-NLS-1$

    throw new IllegalStateException(
        ProxyRemoteMessages.REMProxyFactoryRegistry_CouldNotCreateSocketConnectionToRemoteVM_EXC_);
  }

  /** Free the connection */
  public void returnConnection(IREMConnection connection) {
    if (connection.isConnected()) {
      Thread thread = Thread.currentThread();
      if (!(thread instanceof REMCallbackThread)
          || ((REMCallbackThread) thread).getConnection() != connection) {
        // We are not a callback thread, or we are but the connection is not for the thread, then
        // the connection
        // can be returned.
        synchronized (fConnectionPool) {
          if (fConnectionPool.size() < NUMBER_FREE_CONNECTIONS) fConnectionPool.push(connection);
          else connection.close(); // We don't need to maintain more than five free connections.
        }
      }
    }
  }

  /** Release this connection. This means close it out. */
  public void closeConnection(IREMConnection connection) {
    connection.close();
  }

  public int connectionCount() {
    synchronized (fConnectionPool) {
      return fConnectionPool.size();
    }
  }
}