Package org.eclipse.php.internal.debug.core.zend.communication

Source Code of org.eclipse.php.internal.debug.core.zend.communication.DebugConnectionThread$DummyDebugMessage

/*******************************************************************************
* Copyright (c) 2009 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*     IBM Corporation - initial API and implementation
*     Zend Technologies
*******************************************************************************/
package org.eclipse.php.internal.debug.core.zend.communication;

import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;

import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.internal.core.LaunchManager;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.php.debug.core.debugger.handlers.IDebugMessageHandler;
import org.eclipse.php.debug.core.debugger.handlers.IDebugRequestHandler;
import org.eclipse.php.debug.core.debugger.messages.IDebugMessage;
import org.eclipse.php.debug.core.debugger.messages.IDebugNotificationMessage;
import org.eclipse.php.debug.core.debugger.messages.IDebugRequestMessage;
import org.eclipse.php.debug.core.debugger.messages.IDebugResponseMessage;
import org.eclipse.php.debug.core.debugger.parameters.IDebugParametersKeys;
import org.eclipse.php.internal.core.util.BlockingQueue;
import org.eclipse.php.internal.core.util.collections.IntHashtable;
import org.eclipse.php.internal.debug.core.IPHPDebugConstants;
import org.eclipse.php.internal.debug.core.Logger;
import org.eclipse.php.internal.debug.core.PHPDebugCoreMessages;
import org.eclipse.php.internal.debug.core.PHPDebugPlugin;
import org.eclipse.php.internal.debug.core.launching.PHPLaunchUtilities;
import org.eclipse.php.internal.debug.core.launching.PHPProcess;
import org.eclipse.php.internal.debug.core.preferences.PHPDebugCorePreferenceNames;
import org.eclipse.php.internal.debug.core.preferences.PHPProjectPreferences;
import org.eclipse.php.internal.debug.core.zend.debugger.DebugMessagesRegistry;
import org.eclipse.php.internal.debug.core.zend.debugger.PHPSessionLaunchMapper;
import org.eclipse.php.internal.debug.core.zend.debugger.RemoteDebugger;
import org.eclipse.php.internal.debug.core.zend.debugger.messages.*;
import org.eclipse.php.internal.debug.core.zend.debugger.parameters.AbstractDebugParametersInitializer;
import org.eclipse.php.internal.debug.core.zend.model.PHPDebugTarget;
import org.eclipse.php.internal.debug.core.zend.testConnection.DebugServerTestController;
import org.eclipse.php.internal.debug.core.zend.testConnection.DebugServerTestEvent;
import org.eclipse.php.internal.server.core.Server;
import org.eclipse.swt.widgets.Display;

import com.ibm.icu.text.MessageFormat;

/**
* The debug connection thread is responsible of initializing and handle a
* single debug session that was triggered by a remote or local debugger.
*
* @author shalom
*/
public class DebugConnectionThread implements Runnable {

  protected static int startMessageId = (new DebugSessionStartedNotification())
      .getType();
  private Socket socket;
  private DataInputStream connectionIn;
  private DataOutputStream connectionOut;
  protected boolean validProtocol;
  private boolean isInitialized;
  private InputMessageHandler inputMessageHandler;
  private CommunicationClient communicationClient;
  private CommunicationAdministrator administrator;
  private IDebugMessage CONNECTION_CLOSED_MSG = new DummyDebugMessage();
  private IntHashtable requestsTable;
  private IntHashtable responseTable;
  private Hashtable<Integer, ResponseHandler> responseHandlers;
  private InputManager inputManager;
  private ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
  private DataOutputStream dataOutputStream = new DataOutputStream(
      byteArrayOutputStream);
  private int lastRequestID = 1000;
  protected int debugResponseTimeout;
  protected PHPDebugTarget debugTarget;
  private Thread theThread;
  private Map<Integer, IDebugMessageHandler> messageHandlers;

  /**
   * Constructs a new DebugConnectionThread with a given Socket.
   *
   * @param socket
   */
  public DebugConnectionThread(Socket socket) {

    Preferences prefs = PHPProjectPreferences.getModelPreferences();
    debugResponseTimeout = prefs
        .getInt(PHPDebugCorePreferenceNames.DEBUG_RESPONSE_TIMEOUT);

    this.socket = socket;
    requestsTable = new IntHashtable();
    responseTable = new IntHashtable();
    responseHandlers = new Hashtable<Integer, ResponseHandler>();
    messageHandlers = new HashMap<Integer, IDebugMessageHandler>();
    theThread = new Thread(this, "PHP Debug Conection"); //$NON-NLS-1$
    theThread.start();
  }

  /*
   * (non-Javadoc)
   *
   * @see java.lang.Runnable#run()
   */
  public void run() {
    try {
      socket.setTcpNoDelay(true);
      DataInputStream in = new DataInputStream(socket.getInputStream());
      DataOutputStream out = new DataOutputStream(socket
          .getOutputStream());
      synchronized (in) {
        // These 2 methods are blocked until the threads
        // will stop working and will be free to handle
        // another connection.
        this.connectionIn = in;
        this.connectionOut = out;
        restartInputMessageHandler(out);
        restartInputManager(in);
        isInitialized = true;
      }
      // getCommunicationAdministrator().connectionEstablished();
    } catch (Exception exc) {
      PHPDebugPlugin.log(exc);
    }
  }

  public void setCommunicationClient(CommunicationClient client) {
    this.communicationClient = client;
  }

  public CommunicationClient getCommunicationClient() {
    return communicationClient;
  }

  public void setCommunicationAdministrator(CommunicationAdministrator admin) {
    administrator = admin;
  }

  public CommunicationAdministrator getCommunicationAdministrator() {
    return administrator;
  }

  private IDebugMessageHandler createMessageHandler(IDebugMessage message) {
    if (!messageHandlers.containsKey(message.getType())) {
      IDebugMessageHandler requestHandler = DebugMessagesRegistry
          .getHandler(message);
      messageHandlers.put(message.getType(), requestHandler);
    }
    return messageHandlers.get(message.getType());
  }

  /**
   * Deliver a Notification.
   *
   * @param msg
   *            The delivered notification message.
   */
  public void sendNotification(Object msg) {
    try {
      synchronized (byteArrayOutputStream) {
        byteArrayOutputStream.reset();
        ((IDebugMessage) msg).serialize(dataOutputStream);
        synchronized (connectionOut) {
          connectionOut.writeInt(byteArrayOutputStream.size());
          byteArrayOutputStream.writeTo(connectionOut);
          connectionOut.flush();
        }
      }
    } catch (SocketException se) {
      // probably because the remote host disconnected
      // Just log a warning (might be removed in the near future)
      if (PHPDebugPlugin.DEBUG) {
        Logger.log(Logger.WARNING, se.getMessage(), se);
      }
    } catch (Exception exc) {
      PHPDebugPlugin.log(exc);
    }
  }

  /**
   * Deliver a request & wait for a response
   *
   * @param request
   *            The delivered Request message.
   * @return A response for the delivered request.
   */
  public Object sendRequest(Object request) throws Exception {
    if (PHPDebugPlugin.DEBUG) {
      System.out.println("Sending syncrhonic request: " + request); //$NON-NLS-1$
    }
    try {
      IDebugRequestMessage theMsg = (IDebugRequestMessage) request;
      synchronized (byteArrayOutputStream) {
        byteArrayOutputStream.reset();
        theMsg.setID(lastRequestID++);
        theMsg.serialize(dataOutputStream);

        int messageSize = byteArrayOutputStream.size();
        synchronized (connectionOut) {
          requestsTable.put(theMsg.getID(), theMsg);
          connectionOut.writeInt(messageSize);
          byteArrayOutputStream.writeTo(connectionOut);
          connectionOut.flush();
        }
      }

      IDebugResponseMessage response = null;
      int timeoutTick = 500; // 0.5 of second
      int waitedTime = 0;
      while (response == null && isConnected()) {
        synchronized (request) {
          response = (IDebugResponseMessage) responseTable
              .remove(theMsg.getID());
          if (response == null) {
            if (PHPDebugPlugin.DEBUG) {
              System.out
                  .println("Response is null. Waiting " + waitedTime + " milliseconds"); //$NON-NLS-1$ //$NON-NLS-2$
            }
            if (waitedTime > debugResponseTimeout / 4) { // Display
                                    // a
                                    // progress
                                    // dialog
                                    // after
                                    // a
                                    // quarter
                                    // of
                                    // the
                                    // assigned
                                    // time
                                    // have
                                    // passed.
              // Display a message that we are waiting for the
              // server response.
              // In case that the response finally arrives, remove
              // the message.
              // In case we have a timeout, close the connection
              // and display a different message.
              PHPLaunchUtilities.showWaitForDebuggerMessage(this);
            }
            request.wait(timeoutTick);
          }
        }
        if (response == null) {
          response = (IDebugResponseMessage) responseTable
              .remove(theMsg.getID());
        }

        // if the response is null. it means that there is no answer
        // from the server.
        // This can be because on the peerResponseTimeout.
        if (response == null && isConnected()) {
          if (PHPDebugPlugin.DEBUG) {
            System.out
                .println("Communication problems (response is null)"); //$NON-NLS-1$
          }
          // Handle time out will stop the communication if need to
          // stop.

          if (waitedTime < debugResponseTimeout - timeoutTick) {
            waitedTime += timeoutTick;
            handlePeerResponseTimeout();
          } else {
            closeConnection();
            PHPLaunchUtilities.hideWaitForDebuggerMessage();
            PHPLaunchUtilities.showLaunchErrorMessage();
          }
          if (!isConnected())
            break;
        }
      }
      PHPLaunchUtilities.hideWaitForDebuggerMessage();
      if (PHPDebugPlugin.DEBUG) {
        System.out.println("Received response: " + response); //$NON-NLS-1$
      }
      return response;

    } catch (IOException e) { // Return null for any exception
      PHPDebugPlugin.log(e);
    } catch (InterruptedException e) {// Return null for any exception
      PHPDebugPlugin.log(e);
    }
    return null;
  }

  /**
   *
   * @param request
   * @param responseHandler
   */
  public void sendRequest(Object request, ResponseHandler responseHandler) {
    if (PHPDebugPlugin.DEBUG) {
      System.out.println("Sending asynchronic request: " + request); //$NON-NLS-1$
    }
    int msgId = lastRequestID++;
    IDebugRequestMessage theMsg = (IDebugRequestMessage) request;
    try {
      ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
      DataOutputStream dataOutputStream = new DataOutputStream(
          byteArrayOutputStream);
      theMsg.setID(msgId);
      theMsg.serialize(dataOutputStream);

      int messageSize = byteArrayOutputStream.size();
      synchronized (connectionOut) {
        requestsTable.put(msgId, request);
        responseHandlers.put(Integer.valueOf(msgId), responseHandler);
        connectionOut.writeInt(messageSize);
        byteArrayOutputStream.writeTo(connectionOut);
        connectionOut.flush();
      }
    } catch (Exception exc) { // Return null for any exception
      System.out
          .println("Exception for request no." + theMsg.getType() + exc.toString()); //$NON-NLS-1$
      responseHandler.handleResponse(request, null);
      responseHandlers.remove(Integer.valueOf(msgId));
    }
  }

  /**
   * Returns true if the connection is alive.
   */
  public boolean isConnected() {
    if (connectionIn != null) {
      try {
        connectionIn.available();
        return true;
      } catch (IOException ioe) {
      }
    }
    return false;
  }

  /**
   * Closes the connection. Destroys the server socket without cleaning the
   * the request/response tables. The cleaning will be done by
   * InputMessageHandler on termination.
   */
  public synchronized void closeConnection() {
    Logger.debugMSG("DebugConnectionThread: Clean socket"); //$NON-NLS-1$ //$NON-NLS-2$
    cleanSocket();

    if (theThread.isAlive()) {
      Logger.debugMSG("DebugConnectionThread: Thread interrupt"); //$NON-NLS-1$ //$NON-NLS-2$
      theThread.interrupt();
    }

    if (socket != null) {
      try {
        if (!socket.isClosed()) {
          Logger.debugMSG("DebugConnectionThread: closing the socket"); //$NON-NLS-1$ //$NON-NLS-2$
          socket.close();
        }
      } catch (Exception exc) {
        PHPDebugPlugin.log(exc);
      }
      socket = null;
    }

    // TODO - Check: Commented out - Causing for the second launch of debug
    // to hang

    /*
     * Logger.debugMSG("DebuggerCommunicationKit: Terminating inputManager");
     * if (inputManager != null && inputManager.isAlive()) {
     * inputManager.terminate(); }Logger.debugMSG(
     * "DebuggerCommunicationKit: Terminating inputMessageHandler"); if
     * (inputMessageHandler != null && inputMessageHandler.isAlive()) {
     * inputMessageHandler.terminate(); }
     */
  }

  /**
   * This method initializes the input message handler thread. If it exists it
   * reinitializes the thread.
   */
  protected void restartInputMessageHandler(DataOutputStream out) {
    if (inputMessageHandler == null) {
      inputMessageHandler = new InputMessageHandler(out);
      inputMessageHandler.start();
    } else {
      inputMessageHandler.waitForStart(out, true);
    }
  }

  /**
   * This method initializes the input manager thread. If it exists it
   * reinitializes the thread.
   */
  protected void restartInputManager(DataInputStream in) {
    try {
      if (inputManager == null) {
        inputManager = new InputManager(in);
        inputManager.start();
      } else {
        inputManager.waitForStart(in);
      }
    } catch (Exception exc) {
      PHPDebugPlugin.log(exc);
    }
  }

  /**
   * Reads and returns the message type from the data input stream.
   *
   * @return The message type id.
   * @throws IOException
   */
  protected short getMessageType(DataInputStream in) throws IOException {
    return in.readShort();
  }

  /**
   * Destroys the socket and initialize it to null.
   */
  protected void cleanSocket() {
    if (!isInitialized) {
      return;
    }
    if (socket != null) {
      try {
        socket.shutdownInput();
      } catch (Exception exc) {
      }

      try {
        socket.shutdownOutput();
      } catch (Exception exc) {
      }
    }
    if (connectionOut != null) {
      try {
        synchronized (connectionOut) {
          connectionOut.close();
        }
      } catch (Exception exc) {
      }
    }

    if (socket != null) {
      try {
        socket.close();
      } catch (Exception exc) {
      }
    }
    if (connectionIn != null) {
      try {
        connectionIn.close();
      } catch (Exception exc) {
      }
    }

    socket = null;
    connectionIn = null;
    if (connectionOut != null) {
      synchronized (connectionOut) {
        connectionOut = null;
      }
    }
  }

  /**
   * In case of a peerResponseTimeout exception we let the communication
   * client handle the logic of the peerResponseTimeout.
   */
  private void handlePeerResponseTimeout() {
    getCommunicationClient().handlePeerResponseTimeout();
  }

  /**
   * Extract the session id from the query. Return -1 if no session id was
   * located.
   *
   * @param query
   * @return The session id, or -1 if non was located in the query.
   */
  protected int getSessionID(String query) {
    int indx = query
        .lastIndexOf(AbstractDebugParametersInitializer.DEBUG_SESSION_ID
            + "="); //$NON-NLS-1$
    if (indx < 0) {
      return -1;
    }
    indx += AbstractDebugParametersInitializer.DEBUG_SESSION_ID.length() + 1;
    query = query.substring(indx);
    indx = query.indexOf('&');
    if (indx > -1) {
      return Integer.parseInt(query.substring(0, indx));
    }
    return Integer.parseInt(query.trim());
  }

  /**
   * Hook the debug session to the correct ILaunch that started it. In case
   * there is no such launch, the user will have to fill in some of the
   * details needed to start a debug session correctly.
   *
   * @param debugSessionStartedNotification
   * @return True, if the debug session hook was successful; False, otherwise.
   */
  public boolean hookDebugSession(
      DebugSessionStartedNotification debugSessionStartedNotification)
      throws CoreException {
    String query = debugSessionStartedNotification.getQuery();
    int sessionID = getSessionID(query);
    if (sessionID == 0) {
      sessionID = getSessionID(debugSessionStartedNotification
          .getOptions());
    }
    // Get the launch, but keep it in the mapper for any other debug
    // requests that are
    // related to the debug session id.
    // The launch is mapped until the launches are cleared.
    ILaunch launch = PHPSessionLaunchMapper.get(sessionID);
    if (launch == null) {
      // We cannot find a launch the we can associate to the given session
      // id (if any)
      // Try to take the first launch that is terminated and has a 'Debug
      // all Pages' attribute.
      ILaunch[] launchs = DebugPlugin.getDefault().getLaunchManager()
          .getLaunches();
      for (ILaunch aLaunch : launchs) {
        String debugType = aLaunch
            .getAttribute(IPHPDebugConstants.DEBUGGING_PAGES);
        if (aLaunch.isTerminated()
            && (IPHPDebugConstants.DEBUGGING_ALL_PAGES
                .equals(debugType) || IPHPDebugConstants.DEBUGGING_START_FROM
                .equals(debugType))) {
          launch = aLaunch;
          break;
        }
      }
    }
    if (launch != null) {
      // Remove any debug targets and processes that were terminated.
      IDebugTarget[] debugTargets = launch.getDebugTargets();
      IProcess[] processes = launch.getProcesses();
      for (IDebugTarget element : debugTargets) {
        if (element.isTerminated()) {
          launch.removeDebugTarget(element);
        } else {
          // Do not allow any other targets or processes when an
          // active debug target exists
          return true;
        }
      }
      for (IProcess element : processes) {
        if (element.isTerminated()) {
          launch.removeProcess(element);
        }
      }

      if (Boolean
          .toString(true)
          .equals(
              launch.getAttribute(IDebugParametersKeys.WEB_SERVER_DEBUGGER))) {
        hookServerDebug(launch, debugSessionStartedNotification);
      } else {
        hookPHPExeDebug(launch, debugSessionStartedNotification);
      }
      return true;
    }
    return handleHookError("No session id"); //$NON-NLS-1$
  }

  /**
   * Handle a debug session hook error. This method can be subclassed for
   * handling more complex causes. The default implementation is to display
   * the toString() value of the cause and return false.
   *
   * @param cause
   *            An object that represents the cause for the error. Can be a
   *            String description or a different complex object that can
   *            supply more information.
   * @return True, if the error was fixed in this method; False, otherwise.
   */
  protected boolean handleHookError(Object cause) {
    if (cause != null) {
      Logger.log(Logger.ERROR, cause.toString());
    } else {
      Logger.log(Logger.ERROR, "Debug hook error"); //$NON-NLS-1$
    }
    return false;
  }

  /**
   * Hook a server debug session
   *
   * @param launch
   *            An {@link ILaunch}
   * @param startedNotification
   *            A DebugSessionStartedNotification
   */
  protected void hookServerDebug(final ILaunch launch,
      DebugSessionStartedNotification startedNotification)
      throws CoreException {
    ILaunchConfiguration launchConfiguration = launch
        .getLaunchConfiguration();

    IProject project = getProject(launchConfiguration);

    inputManager.setTransferEncoding(launchConfiguration.getAttribute(
        IDebugParametersKeys.TRANSFER_ENCODING, "")); //$NON-NLS-1$
    inputManager.setOutputEncoding(launchConfiguration.getAttribute(
        IDebugParametersKeys.OUTPUT_ENCODING, "")); //$NON-NLS-1$
    String URL = getURL(launchConfiguration);

    boolean stopAtFirstLine = project == null ? true
        : PHPProjectPreferences.getStopAtFirstLine(project);
    int requestPort = PHPDebugPlugin
        .getDebugPort(DebuggerCommunicationDaemon.ZEND_DEBUGGER_ID);
    boolean runWithDebug = launchConfiguration.getAttribute(
        IPHPDebugConstants.RUN_WITH_DEBUG_INFO, true);
    if (launch.getLaunchMode().equals(ILaunchManager.DEBUG_MODE)) {
      runWithDebug = false;
    }
    PHPProcess process = new PHPProcess(launch, URL);
    debugTarget = (PHPDebugTarget) createDebugTraget(this, launch, URL,
        requestPort, process, runWithDebug, stopAtFirstLine, project);
    launch.addDebugTarget(debugTarget);
    // A fix for Linux display problem.
    // This code will auto-expand the debugger view tree.
    Display.getDefault().asyncExec(new Runnable() {
      public void run() {
        LaunchManager manager = (LaunchManager) DebugPlugin
            .getDefault().getLaunchManager();
        manager.fireUpdate(new ILaunch[] { launch },
            LaunchManager.ADDED);
        // Added to fix a problem while migrating to 3.3.
        // The stack tree was not updated.
        manager.fireUpdate(new ILaunch[] { launch },
            LaunchManager.CHANGED);
      }
    });
  }

  /**
   * Creates a new IDebugTarget. This create method is usually used when
   * hooking a PHP web page launch.
   *
   * @throws CoreException
   */
  protected IDebugTarget createDebugTraget(DebugConnectionThread thread,
      ILaunch launch, String url, int requestPort, PHPProcess process,
      boolean runWithDebug, boolean stopAtFirstLine, IProject project)
      throws CoreException {
    return new PHPDebugTarget(thread, launch, url, requestPort, process,
        runWithDebug, stopAtFirstLine, project);
  }

  /**
   * Creates a new IDebugTarget. This create method is usually used when
   * hooking a PHP executable launch.
   *
   * @throws CoreException
   */
  protected IDebugTarget createDebugTarget(DebugConnectionThread thread,
      ILaunch launch, String phpExeString, String debugFileName,
      int requestPort, PHPProcess process, boolean runWithDebugInfo,
      boolean stopAtFirstLine, IProject project) throws CoreException {
    return new PHPDebugTarget(thread, launch, phpExeString, debugFileName,
        requestPort, process, runWithDebugInfo, stopAtFirstLine,
        project);
  }

  /**
   * Hook a PHP executable debug session
   *
   * @param launch
   *            An {@link ILaunch}
   * @param startedNotification
   *            A DebugSessionStartedNotification
   */
  protected void hookPHPExeDebug(final ILaunch launch,
      DebugSessionStartedNotification startedNotification)
      throws CoreException {
    ILaunchConfiguration launchConfiguration = launch
        .getLaunchConfiguration();
    inputManager.setTransferEncoding(launchConfiguration.getAttribute(
        IDebugParametersKeys.TRANSFER_ENCODING, "")); //$NON-NLS-1$
    inputManager.setOutputEncoding(launchConfiguration.getAttribute(
        IDebugParametersKeys.OUTPUT_ENCODING, "")); //$NON-NLS-1$
    String phpExeString = launchConfiguration.getAttribute(
        IPHPDebugConstants.ATTR_EXECUTABLE_LOCATION, (String) null);
    String fileNameString = launchConfiguration.getAttribute(
        IPHPDebugConstants.ATTR_FILE, (String) null);
    boolean runWithDebugInfo = launchConfiguration.getAttribute(
        IPHPDebugConstants.RUN_WITH_DEBUG_INFO, true);

    IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
    IProject project = null;
    String file = launchConfiguration.getAttribute(
        IPHPDebugConstants.ATTR_FILE, (String) null);
    if (file != null) {
      IResource resource = workspaceRoot.findMember(file);
      if (resource != null) {
        project = resource.getProject();
      }
    }

    if (launch.getLaunchMode().equals(ILaunchManager.DEBUG_MODE)) {
      runWithDebugInfo = false;
    }

    String debugFileName = fileNameString;

    IPath filePath = new Path(fileNameString);
    IResource res = workspaceRoot.findMember(filePath);
    if (res != null) {
      IFile fileToDebug = (IFile) res;
      debugFileName = fileToDebug.getName();
    }

    boolean stopAtFirstLine = PHPProjectPreferences
        .getStopAtFirstLine(project);
    int requestPort = PHPDebugPlugin
        .getDebugPort(DebuggerCommunicationDaemon.ZEND_DEBUGGER_ID);

    IPath phpExe = new Path(phpExeString);
    PHPProcess process = new PHPProcess(launch, phpExe.toOSString());

    debugTarget = (PHPDebugTarget) createDebugTarget(this, launch,
        phpExeString, debugFileName, requestPort, process,
        runWithDebugInfo, stopAtFirstLine, project);
    launch.addDebugTarget(debugTarget);
    // A fix for Linux display problem.
    // This code will auto-expand the debugger view tree.
    Display.getDefault().asyncExec(new Runnable() {
      public void run() {
        LaunchManager manager = (LaunchManager) DebugPlugin
            .getDefault().getLaunchManager();
        manager.fireUpdate(new ILaunch[] { launch },
            LaunchManager.ADDED);
      }
    });
  }

  /**
   * This thread handles the requests and notification that are inserted into
   * the Queues by the InputManager thread.
   */
  private class InputMessageHandler implements Runnable {

    private BlockingQueue inputMessageQueue = new BlockingQueue(100);

    private boolean shouldExit = false;
    private boolean isAlive = true;
    private boolean inWork = true;
    private Thread theThread;
    private DataOutputStream out;
    private DummyDebugMessage STOP_MSG = new DummyDebugMessage();

    private ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
    private DataOutputStream outArray = new DataOutputStream(byteArray);

    private Object WAIT = new Object();
    private Object READY_FOR_RESTART_LOCK = new Object();

    public InputMessageHandler(DataOutputStream out) {
      this.out = out;
      isAlive = true;
      inWork = true;
      shouldExit = false;
    }

    public void start() {
      theThread = new Thread(this, "PHP Debug Message Handler"); //$NON-NLS-1$
      // This makes the printing much faster.
      theThread.setPriority(1);
      theThread.start();
    }

    /**
     * start the thread on a new output. This will block until end of
     * processing of all the messages.
     */
    public synchronized void start(DataOutputStream out, boolean clearQueue) {
      if (clearQueue)
        inputMessageQueue.clear();
      this.out = out;
      ensureStarted();
    }

    /**
     * This function will block the calling thread until the the
     * InputMessageHandler becomes inactive.
     */
    public void waitForStart(DataOutputStream out, boolean clearQueue) {
      if (inWork) {
        synchronized (READY_FOR_RESTART_LOCK) {
          try {
            READY_FOR_RESTART_LOCK.wait();
          } catch (InterruptedException e) {
          }
        }
      }
      start(out, clearQueue);
    }

    /* just make sure the thread is working */
    public synchronized void ensureStarted() {
      synchronized (WAIT) {
        inWork = true;
        isAlive = true;
        shouldExit = false;
        WAIT.notifyAll();
      }
    }

    public synchronized void stopImmediately(boolean clearQueue) {
      if (!inWork)
        return;
      inWork = false;
      isAlive = true;
      inputMessageQueue.releaseReaders();
      if (clearQueue)
        inputMessageQueue.clear();
      synchronized (WAIT) {
        WAIT.notifyAll();
      }
    }

    public void terminate() {
      inWork = false;
      isAlive = false;
      validProtocol = false;
      inputMessageQueue.releaseReaders();
      inputMessageQueue.clear();
      theThread.interrupt();
    }

    public boolean isAlive() {
      return isAlive;
    }

    public void queueIn(IDebugMessage m) {
      inputMessageQueue.queueIn(m);
    }

    /**
     * This method is called by the input manager so that the message
     * handler will queueIn an internal protocol message for the closure of
     * connection.
     */
    public synchronized void connectionClosed() {
      ensureStarted(); // make sure the thread will process the stop
                // message
      queueIn(CONNECTION_CLOSED_MSG);
    }

    @SuppressWarnings("unchecked")
    private synchronized void resetCommunication() {
      // Now we can stop the input manager.
      if (inputManager != null)
        inputManager.stop();

      // first stop the input messages handler. So if the
      // inputMessagesHandler
      // is sending a request the inputManager will exists to read the
      // response.
      stopImmediately(true);

      synchronized (requestsTable) {
        Iterator i = requestsTable.values().iterator();
        while (i.hasNext()) {
          IDebugRequestMessage r = (IDebugRequestMessage) i.next();
          synchronized (r) {
            r.notifyAll();
          }
        }
      }

      requestsTable.clear();
      responseTable.clear();
      responseHandlers.clear();
    }

    public void run() {

      while (isAlive) {

        while (!inWork && isAlive) {
          try {
            synchronized (WAIT) {
              synchronized (READY_FOR_RESTART_LOCK) {
                READY_FOR_RESTART_LOCK.notify(); // release one
                                  // thread.
              }
              WAIT.wait();
            }

          } catch (InterruptedException e) {
          }
        }

        if (!isAlive)
          break;

        try {
          IDebugMessage newInputMessage = (IDebugMessage) inputMessageQueue
              .queueOut();

          if (PHPDebugPlugin.DEBUG) {
            System.out.println("New message received: " //$NON-NLS-1$
                + newInputMessage);
          }

          // do not stop until the message is processed.
          synchronized (this) {
            try {
              boolean isDebugConnectionTest = false;
              // first debug message has received
              if (newInputMessage instanceof DebugSessionStartedNotification) {

                DebugSessionStartedNotification sessionStartedMessage = (DebugSessionStartedNotification) newInputMessage;
                isDebugConnectionTest = isDebugConnectionTest(sessionStartedMessage);

                if (isDebugConnectionTest) {// This is a test...

                  String sourceHost = DebugConnectionThread.this.socket
                      .getInetAddress().getHostAddress(); //$NON-NLS-1$
                  // Notify succcess
                  if (verifyProtocolID(sessionStartedMessage
                      .getServerProtocolID())) {
                    sendRequest(new StartRequest());
                    DebugServerTestController
                        .getInstance()
                        .notifyTestListener(
                            new DebugServerTestEvent(
                                sourceHost,
                                DebugServerTestEvent.TEST_SUCCEEDED));
                  } else {
                    DebugServerTestController
                        .getInstance()
                        .notifyTestListener(
                            new DebugServerTestEvent(
                                sourceHost,
                                DebugServerTestEvent.TEST_FAILED_DEBUGER_VERSION));
                  }
                  // sendNotification(new
                  // DebugSessionClosedNotification());

                } else {// Not a test - start debug (create
                    // debug target)
                  hookDebugSession((DebugSessionStartedNotification) newInputMessage);
                }
              }

              // creation of debug session has succeeded
              if (debugTarget != null) {

                // try to find relevant handler for the message:
                IDebugMessageHandler messageHandler = createMessageHandler(newInputMessage);

                if (messageHandler != null) {
                  if (PHPDebugPlugin.DEBUG) {
                    System.out
                        .println("Creating message handler: " //$NON-NLS-1$
                            + messageHandler
                                .getClass()
                                .getName()
                                .replaceFirst(
                                    ".*\\.", //$NON-NLS-1$
                                    "")); //$NON-NLS-1$
                  }
                  // handle the request
                  messageHandler.handle(newInputMessage,
                      debugTarget);

                  if (messageHandler instanceof IDebugRequestHandler) {
                    // create response
                    IDebugResponseMessage response = ((IDebugRequestHandler) messageHandler)
                        .getResponseMessage();

                    // send response
                    byteArray.reset();
                    response.serialize(outArray);

                    synchronized (out) {
                      out.writeInt(byteArray.size());
                      byteArray.writeTo(out);
                      out.flush();
                    }
                  }
                } else if (newInputMessage instanceof IDebugResponseMessage) {
                  IDebugResponseMessage r = (IDebugResponseMessage) newInputMessage;
                  int requestId = r.getID(); // take the
                                // request ID
                                // from the
                                // response.
                  IDebugRequestMessage req = (IDebugRequestMessage) requestsTable
                      .remove(requestId); // find the
                                // request.
                  ResponseHandler handler = responseHandlers
                      .remove(Integer.valueOf(requestId)); // find
                                        // the
                                        // handler.
                  handler.handleResponse(req, r);
                } else if (newInputMessage == STOP_MSG) {
                  synchronized (STOP_MSG) {
                    inWork = false;
                    STOP_MSG.notifyAll();
                    if (shouldExit) {
                      isAlive = false;
                      inputMessageQueue.releaseReaders(); // why
                                        // do
                                        // we
                                        // need
                                        // this??
                      // notifyAll();
                    }
                  }
                } else if (newInputMessage == CONNECTION_CLOSED_MSG) {
                  handleConnectionClosed();
                }
              } else { // no debug target - probably creation of
                    // debug session hasn't succeeded
                handleConnectionClosed();
              }
            } catch (Exception e) { // error processing the current
                        // message.
              PHPDebugPlugin.log(e);
            }
          }

        } catch (Exception e) {
          PHPDebugPlugin.log(e);
        }
      }
    }

    private boolean isDebugConnectionTest(
        DebugSessionStartedNotification inputMessage) {
      return inputMessage.getQuery().indexOf("testConnection=true") != -1; //$NON-NLS-1$
    }

    private void handleConnectionClosed() {
      resetCommunication();
      if (getCommunicationAdministrator() != null) {
        getCommunicationAdministrator().connectionClosed();
      }
      terminate();
      closeConnection();
    }
  }

  /**
   * This method checks whether the server protocol is older than the latest
   * Studio protocol.
   *
   * @return <code>true</code> if debugger protocol matches the Studio
   *         protocol, otherwise <code>false</code>
   */
  protected boolean verifyProtocolID(int serverProtocolID) {
    if (serverProtocolID < RemoteDebugger.PROTOCOL_ID_LATEST) {
      return setProtocol(RemoteDebugger.PROTOCOL_ID_LATEST);
    }
    return true;
  }

  protected boolean setProtocol(int protocolID) {
    SetProtocolRequest request = new SetProtocolRequest();
    request.setProtocolID(protocolID);
    try {
      Object response = sendRequest(request);
      if (response != null && response instanceof SetProtocolResponse) {
        int responceProtocolID = ((SetProtocolResponse) response)
            .getProtocolID();
        if (responceProtocolID == protocolID) {
          return true;
        }
      }
    } catch (Exception e) {
      Logger.logException(e);
    }
    return false;
  }

  /**
   * Get {@link IProject} instance from provided launch configuration.
   *
   * @param configuration
   * @return {@link IProject}
   * @throws CoreException
   */
  protected IProject getProject(ILaunchConfiguration configuration)
      throws CoreException {
    String projectName = configuration.getAttribute(
        IPHPDebugConstants.PHP_Project, (String) null);
    if (projectName != null) {
      return ResourcesPlugin.getWorkspace().getRoot()
          .getProject(projectName);
    }
    return null;
  }

  /**
   * Get url from provided launch configuration.
   *
   * @param configuration
   * @return
   * @throws CoreException
   */
  protected String getURL(ILaunchConfiguration configuration)
      throws CoreException {
    return configuration.getAttribute(Server.BASE_URL, ""); //$NON-NLS-1$
  }

  /**
   * This thread manages the Communication initiated by the peer. All the
   * messages that arrive form the peer are read by the ImputManager. The
   * InputManager will then handle the message by the message type. It the
   * message type is a {@link IDebugRequestMessage}. The input manager will
   * also write out the {@link IDebugResponseMessage}
   */
  private class InputManager implements Runnable {

    private DataInputStream in;

    private boolean inWork = false;
    private boolean isAlive = true;
    private Thread theThread;
    private Object READY_FOR_RESTART_LOCK = new Object();
    private String transferEncoding;
    private String outputEncoding;

    /**
     * Sets the transfer encoding.
     *
     * @param transferEncoding
     */
    public void setTransferEncoding(String transferEncoding) {
      this.transferEncoding = transferEncoding;
    }

    /**
     * Set the debug output encoding. The output encoding effects the
     * {@link OutputNotification} strings encoding.
     *
     * @param outputEncoding
     */
    public void setOutputEncoding(String outputEncoding) {
      this.outputEncoding = outputEncoding;
    }

    /**
     * Create an InputManager in a separate thread.
     */
    InputManager(DataInputStream in) {
      this.in = in;
      inWork = true;
      isAlive = true;
    }

    public void start() {
      theThread = new Thread(this, "PHP Debug Message Reader"); //$NON-NLS-1$
      theThread.start();
      // Log.writeLog("Input Manager is started");
    }

    /*
     * This method will block the reader until the end of processing of the
     * current message.
     */
    public synchronized void start(DataInputStream in) {
      stop();
      this.in = in;
      inWork = true;

      // on start we do not need to interrupt since we are sure
      // the thread is in wait on this.
      notifyAll();
    }

    public boolean isAlive() {
      return isAlive;
    }

    public void waitForStart(DataInputStream in) {
      if (inWork) {
        synchronized (READY_FOR_RESTART_LOCK) {
          try {
            READY_FOR_RESTART_LOCK.wait();
          } catch (InterruptedException e) {
          }
        }
      }
      start(in);
    }

    /**
     * Stop this thread from reading from the socket without killing it.
     * This method will block the reader until the end of processing of the
     * current message.
     */
    public synchronized void stop() {
      if (!inWork) {
        return;
      }
      inWork = false;
      isAlive = true;
      theThread.interrupt();
    }

    /**
     * Terminate this thread
     */
    public synchronized void terminate() {
      inWork = false;
      isAlive = false;
      theThread.interrupt();
    }

    /**
     * @see java.lang.Thread#run()
     */
    public void run() {
      while (isAlive) {

        while (!inWork && isAlive) {
          try {
            synchronized (this) {
              synchronized (READY_FOR_RESTART_LOCK) {
                READY_FOR_RESTART_LOCK.notify(); // release one
                                  // thread.

              }
              wait();
              // This way we will start working until the
              // connectionCreator
              // has finished updating the in + out.
              synchronized (in) {
              }
            }
          } catch (InterruptedException e) {
            if (PHPDebugPlugin.DEBUG) {
              System.out
                  .println("interrupted: inWork = " + inWork + ", isAlive = " + isAlive); //$NON-NLS-1$ //$NON-NLS-2$
            }
          }
        }

        try {

          if (!isAlive) {
            break;
          }

          // reads the length
          int num = in.readInt();
          if (num < 0) {
            shutDown();
            if (PHPDebugPlugin.DEBUG) {
              System.out
                  .println("Socket error (length is negative): possibly Server is SSL, Client is not."); //$NON-NLS-1$
            }
            Logger.log(Logger.ERROR,
                "Socket error (length is negative): possibly Server is SSL, Client is not."); //$NON-NLS-1$
          }

          // We have a new message. process it !!.
          // This part is synchronized since we do not want the thread
          // to be stopped
          // when in processing of a message.
          synchronized (this) {
            int messageType = getMessageType(in);
            // If this is the first message, the protocol is still
            // held as invalid.
            // Check that the first message has the
            // DebugSessionStartedNotification type. If not, then we
            // can assume that the remote debugger protocol has a
            // different version then expected.
            if (!validProtocol && messageType != startMessageId) {
              // display an error message that the protocol in
              // used is wrong.
              final String errorMessage = MessageFormat
                  .format(
                      PHPDebugCoreMessages.Debugger_Incompatible_Protocol,
                      new Object[] { String
                          .valueOf(RemoteDebugger.PROTOCOL_ID_LATEST) });
              Status status = new Status(IStatus.ERROR,
                  PHPDebugPlugin.getID(),
                  IPHPDebugConstants.INTERNAL_ERROR,
                  errorMessage, null);
              DebugPlugin.log(status);
              Display.getDefault().asyncExec(new Runnable() {
                public void run() {
                  MessageDialog.openError(Display
                      .getDefault().getActiveShell(),
                      "Debugger Error", errorMessage); //$NON-NLS-1$
                }
              });
              shutDown();
              return;
            }
            validProtocol = true;

            IDebugMessage message = DebugMessagesRegistry
                .getMessage(messageType);
            if (message != null) {
              if (message instanceof OutputNotification) {
                message.setTransferEncoding(outputEncoding);
              } else {
                message.setTransferEncoding(transferEncoding);
              }
            }

            // handle the message
            if (message instanceof IDebugNotificationMessage) {
              message.deserialize(in);
              // getCommunicationClient().handleNotification((Notification)message);
              // PUT NOTIFICATION TO NOTIFICATION QUEUE
              inputMessageHandler.queueIn(message);
            } else if (message instanceof IDebugResponseMessage) {
              message.deserialize(in);
              int idd = ((IDebugResponseMessage) message).getID();
              // responseQueue.queueIn(message);
              // INSERT RESPONSE TO TABLE AND RELEASE THE THREAD
              // WAITING FOR THE REQUEST
              ResponseHandler handler = responseHandlers
                  .get(Integer.valueOf(idd)); // find the handler.
              if (handler == null) {
                responseTable.put(/* requestId */idd, message);
                IDebugRequestMessage req = (IDebugRequestMessage) requestsTable
                    .remove(idd); // find the request.
                if (req != null) {
                  synchronized (req) {
                    req.notifyAll(); // Notify the response
                              // is here.
                  }
                } else {
                  // Remove this message.
                  responseTable.remove(idd);
                }
              } else {
                inputMessageHandler.queueIn(message);
              }
            } else if (message instanceof IDebugRequestMessage) { // this
                                        // is
                                        // a
                                        // request.
              message.deserialize(in);
              // Response response =
              // getCommunicationClient().handleRequest((Request)message);
              inputMessageHandler.queueIn(message);
            }
          } // end of synchronized part.

        } catch (IOException e) {
          // PHPDebugPlugin.log(e);
          shutDown();
        } catch (Exception e) {
          PHPDebugPlugin.log(e);
        }
      }
    }

    private void shutDown() {
      terminate();
      cleanSocket();
      inputMessageHandler.connectionClosed();
    }
  }

  /**
   * This is a dummy debug protocol message, used for internal needs
   *
   * @author michael
   */
  private class DummyDebugMessage extends DebugMessageImpl {

    public void deserialize(DataInputStream in) throws IOException {
    }

    public int getType() {
      return 0;
    }

    public void serialize(DataOutputStream out) throws IOException {
    }
  }
}
TOP

Related Classes of org.eclipse.php.internal.debug.core.zend.communication.DebugConnectionThread$DummyDebugMessage

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.