/******************************************************************************* * 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.xdebug.dbgp.model; import java.util.ArrayList; import java.util.List; import org.eclipse.core.resources.IMarkerDelta; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.debug.core.*; import org.eclipse.debug.core.model.*; import org.eclipse.php.internal.debug.core.IPHPDebugConstants; import org.eclipse.php.internal.debug.core.PHPDebugCoreMessages; import org.eclipse.php.internal.debug.core.PHPDebugUtil; import org.eclipse.php.internal.debug.core.launching.PHPProcess; import org.eclipse.php.internal.debug.core.model.DebugOutput; import org.eclipse.php.internal.debug.core.model.IPHPDebugTarget; import org.eclipse.php.internal.debug.core.pathmapper.PathMapper; import org.eclipse.php.internal.debug.core.xdebug.dbgp.DBGpBreakpointFacade; import org.eclipse.php.internal.debug.core.xdebug.dbgp.DBGpLogger; import org.eclipse.php.internal.debug.core.xdebug.dbgp.DBGpPreferences; import org.eclipse.php.internal.debug.core.xdebug.dbgp.session.DBGpSession; import org.eclipse.php.internal.debug.core.xdebug.dbgp.session.DBGpSessionHandler; import org.eclipse.php.internal.debug.core.xdebug.dbgp.session.IDBGpSessionListener; public class DBGpMultiSessionTarget extends DBGpElement implements IPHPDebugTarget, IDBGpDebugTarget, IDBGpSessionListener, IDebugEventSetListener { // used to identify this debug target with the associated // script being debugged. private String sessionID; // XXX: never set private String ideKey; private boolean webLaunch = false; // required for EXE target support private IProcess process; private String stopDebugURL; // debug target state private volatile int targetState; // waiting for 1st session private static final int STATE_INIT_SESSION_WAIT = 0; // Target fully started private static final int STATE_STARTED = 1; // ASync stop request made private static final int STATE_TERMINATING = 2; // terminated private static final int STATE_TERMINATED = 3; // the script being run, or initial web script private String scriptName; // launch object private ILaunch launch; private DBGpBreakpointFacade bpFacade; private DBGpPreferences sessionPreferences; private TimedEvent te = new TimedEvent(); // debug config settings private boolean stopAtStart; private ArrayList<DBGpTarget> debugTargets = new ArrayList<DBGpTarget>(); private PathMapper pathMapper; // need to have something in case a target is terminated before // a session is initiated to stop a NPE in the debug view private DebugOutput debugOutput = new DebugOutput(); /** * Base constructor * */ private DBGpMultiSessionTarget() { super(null); ideKey = DBGpSessionHandler.getInstance().getIDEKey(); // listen for debug events DebugPlugin.getDefault().addDebugEventListener(this); fireCreationEvent(); targetState = STATE_INIT_SESSION_WAIT; } /** * target that handles invocation via a web browser * * @param launch * @param workspaceRelativeScript * @param stopDebugURL * @param sessionID * @param stopAtStart */ public DBGpMultiSessionTarget(ILaunch launch, String workspaceRelativeScript, String stopDebugURL, String ideKey, boolean stopAtStart) { this(); this.stopAtStart = stopAtStart; this.launch = launch; this.scriptName = workspaceRelativeScript; this.ideKey = ideKey; this.webLaunch = true; this.sessionID = null; // in the web launch we have no need for the // session ID. createMockProcess(launch, stopDebugURL); } private void createMockProcess(ILaunch launch, String stopDebugURL) { this.stopDebugURL = stopDebugURL; this.process = new PHPProcess(launch, PHPDebugCoreMessages.DBGpMultiSessionTarget_Multisession_PHP_process); this.process.setAttribute(IProcess.ATTR_PROCESS_TYPE, IPHPDebugConstants.PHPProcessType); ((PHPProcess) this.process).setDebugTarget(this); launch.addProcess(process); } /* * (non-Javadoc) * * @see org.eclipse.debug.core.model.IDebugElement#getDebugTarget() */ public IDebugTarget getDebugTarget() { return this; } public ILaunch getLaunch() { return launch; } public String getName() throws DebugException { // Multisession Manager return PHPDebugCoreMessages.XDebug_DBGpMultiSessionTarget_0; } /* * (non-Javadoc) * * @see org.eclipse.debug.core.model.IDebugTarget#getProcess() */ public IProcess getProcess() { return process; } /** * set the process * * @param proc */ public void setProcess(IProcess proc) { process = proc; } public IThread[] getThreads() throws DebugException { // Collect threads from all sub-targets List<IThread> threads = new ArrayList<IThread>(); for (IPHPDebugTarget target : debugTargets) if (target.hasThreads()) for (IThread thread : target.getThreads()) threads.add(thread); return threads.toArray(new IThread[threads.size()]); } public boolean hasThreads() throws DebugException { // Check if any sub-target has at least one thread for (IPHPDebugTarget target : debugTargets) if (target.hasThreads()) return true; return false; } public boolean supportsBreakpoint(IBreakpoint breakpoint) { synchronized (debugTargets) { if (debugTargets.size() > 0) { IDebugTarget firstTarget = debugTargets.get(0); return firstTarget.supportsBreakpoint(breakpoint); } } return false; } public boolean isSuspended() { boolean isSuspended = false; synchronized (debugTargets) { for (int i = 0; i < debugTargets.size() && !isSuspended; i++) { IDebugTarget target = debugTargets.get(i); isSuspended = isSuspended | target.isSuspended(); } } return isSuspended; } public boolean isTerminated() { return targetState == STATE_TERMINATED; } public void terminate() throws DebugException { if (targetState == STATE_TERMINATING) { // we attempt a sledge hammer termination. synchronized (debugTargets) { if (debugTargets.size() > 0) { for (int i = 0; i < debugTargets.size(); i++) { IDebugTarget target = debugTargets.get(i); try { target.terminate(); } catch (Exception e) { } } } // session listening will already have been removed terminateMultiSessionDebugTarget(); } return; } synchronized (debugTargets) { // remove myself as a session listener. DBGpSessionHandler.getInstance().removeSessionListener(this); targetState = STATE_TERMINATING; if (debugTargets.size() > 0) { for (int i = 0; i < debugTargets.size(); i++) { IDebugTarget target = debugTargets.get(i); if (target.canTerminate()) { target.terminate(); } } } else { terminateMultiSessionDebugTarget(); } } } public boolean canDisconnect() { boolean canDisconnect = false; return canDisconnect; } public void disconnect() throws DebugException { } public boolean isDisconnected() { return false; } public boolean canTerminate() { boolean canTerminate = (targetState == STATE_STARTED || targetState == STATE_INIT_SESSION_WAIT); return canTerminate; } public boolean canResume() { boolean canResume = false; synchronized (debugTargets) { for (int i = 0; i < debugTargets.size() && !canResume; i++) { IDebugTarget target = debugTargets.get(i); canResume = canResume | target.canResume(); } } return canResume; } public boolean canSuspend() { boolean canSuspend = false; synchronized (debugTargets) { for (int i = 0; i < debugTargets.size() && !canSuspend; i++) { IDebugTarget target = debugTargets.get(i); canSuspend = canSuspend | target.canSuspend(); } } return canSuspend; } public void resume() throws DebugException { synchronized (debugTargets) { for (int i = 0; i < debugTargets.size(); i++) { IDebugTarget target = debugTargets.get(i); if (target.canResume()) { target.resume(); } } } } public void suspend() throws DebugException { synchronized (debugTargets) { for (int i = 0; i < debugTargets.size(); i++) { IDebugTarget target = debugTargets.get(i); if (target.canSuspend()) { target.suspend(); } } } } public IMemoryBlock getMemoryBlock(long startAddress, long length) throws DebugException { return null; } public boolean supportsStorageRetrieval() { return false; } /** * returns if we have terminated or in the process of terminating * * @return */ private boolean isTerminating() { boolean terminating = (targetState == STATE_TERMINATED) || (targetState == STATE_TERMINATING); return terminating; } public void waitForInitialSession(DBGpBreakpointFacade facade, DBGpPreferences sessionPrefs, IProgressMonitor launchMonitor) { configureInitialState(facade, sessionPrefs); try { while (debugTargets.size() == 0 && !launch.isTerminated() && !isTerminating() && !launchMonitor.isCanceled()) { te.waitForEvent(DBGpPreferences.DBGP_TIMEOUT_DEFAULT); } } catch (InterruptedException e) { } if (debugTargets.size() == 0) { DBGpSessionHandler.getInstance().removeSessionListener(this); terminateMultiSessionDebugTarget(); } } public void sessionReceived(DBGpBreakpointFacade facade, DBGpPreferences sessionPrefs, DBGpTarget owningTarget, PathMapper globalMapper) { configureInitialState(facade, sessionPrefs); owningTarget.setMultiSessionManaged(true); addDebugTarget(owningTarget); setPathMapper(globalMapper); owningTarget.sessionReceived(facade, sessionPrefs); } public void configureInitialState(DBGpBreakpointFacade facade, DBGpPreferences sessionPrefs) { bpFacade = facade; sessionPreferences = sessionPrefs; } public boolean SessionCreated(DBGpSession session) { boolean accepted = false; synchronized (debugTargets) { /* * We need to use single shot debug targets to ensure that they * terminate when complete and don't hang around waiting for another * session they won't receive. */ DBGpTarget target = new DBGpTarget(this.launch, this.scriptName, this.stopDebugURL, this.ideKey, this.sessionID, this.stopAtStart); target.setMultiSessionManaged(true); target.setPathMapper(pathMapper); accepted = target.SessionCreated(session); if (accepted) { /* * Need to make sure bpFacade is thread safe. cannot provide a * launch monitor here, unless this is the first launch, but it * doesn't matter. */ target.waitForInitialSession(bpFacade, sessionPreferences, null); if (!target.isTerminated()) { addDebugTarget(target); if (targetState == STATE_INIT_SESSION_WAIT) { targetState = STATE_STARTED; te.signalEvent(); } } } } return accepted; } public void addDebugTarget(DBGpTarget target) { synchronized (debugTargets) { if (debugTargets.size() == 0) { // this is the first target, so clear out any old debug output // and set up new information. debugOutput = new DebugOutput(); } debugTargets.add(target); } } public void breakpointAdded(IBreakpoint breakpoint) { // Do nothing } public void breakpointChanged(IBreakpoint breakpoint, IMarkerDelta delta) { // do nothing } public void breakpointRemoved(IBreakpoint breakpoint, IMarkerDelta delta) { // do nothing } public void handleDebugEvents(DebugEvent[] events) { synchronized (debugTargets) { for (int i = 0; i < events.length; i++) { DebugEvent evt = events[i]; Object src = evt.getSource(); if (src instanceof DBGpTarget) { if (debugTargets.contains(src)) { // ok it is one of ours, what is the event int kind = evt.getKind(); if (kind == DebugEvent.TERMINATE) { debugTargets.remove(src); } } } } } if (targetState == STATE_TERMINATING && debugTargets.size() == 0) { // session listening was removed when we went to // STATE_TERMINATING terminateMultiSessionDebugTarget(); } } private void terminateMultiSessionDebugTarget() { if (webLaunch) { sendStopDebugURL(); } targetState = STATE_TERMINATED; DebugPlugin.getDefault().removeDebugEventListener(this); fireTerminateEvent(); // Terminate corresponding launch as well try { getLaunch().terminate(); } catch (DebugException e) { } } /** * Sends stop debug session URL. */ private void sendStopDebugURL() { if (stopDebugURL == null) { return; } DBGpLogger.debug("browser is not null, sending " + stopDebugURL); //$NON-NLS-1$ try { PHPDebugUtil.openLaunchURL(stopDebugURL); } catch (DebugException e) { DBGpLogger.logException("Failed to send stop XDebug session URL: " + stopDebugURL, //$NON-NLS-1$ this, e); } } public void setPathMapper(PathMapper mapper) { pathMapper = mapper; } /** * return if this is a web launch * * @return */ public boolean isWebLaunch() { return webLaunch; } public DebugOutput getOutputBuffer() { return debugOutput; } public boolean isWaiting() { boolean isWaiting = (targetState == STATE_INIT_SESSION_WAIT); synchronized (debugTargets) { for (int i = 0; i < debugTargets.size() && !isWaiting; i++) { IPHPDebugTarget target = debugTargets.get(i); isWaiting = isWaiting | target.isWaiting(); } } return isWaiting; } }