/*******************************************************************************
* Copyright © 2008, 2013 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
*
*******************************************************************************/
package org.eclipse.edt.ide.debug.javascript.internal.server;
import java.io.IOException;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.edt.debug.core.IEGLDebugCoreConstants;
import org.eclipse.edt.debug.internal.ui.actions.BreakpointUtils;
import org.eclipse.edt.debug.javascript.internal.model.RUIDebugContextResolver;
import org.eclipse.edt.debug.javascript.internal.model.RUIDebugMessages;
import org.eclipse.edt.debug.javascript.internal.model.RUIDebugTarget;
import org.eclipse.edt.ide.core.model.EGLModelException;
import org.eclipse.edt.ide.debug.javascript.internal.utils.RUIDebugUtil;
import org.eclipse.edt.ide.rui.server.AbstractContext;
import org.eclipse.edt.ide.rui.server.EvServer;
import org.eclipse.edt.ide.rui.server.EvServer.Event;
import org.eclipse.edt.ide.rui.server.IContext2;
import org.eclipse.edt.javart.json.TokenMgrError;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.widgets.Display;
public class DebugContext extends AbstractContext implements IContext2
{
private RUIDebugTarget debugTarget;
private List<String> eventQueue;
private final String launchConfigName;
/**
* High priority events jump to the front - even ahead of other high priority events. Normal priority events are added to the end of the list.
*/
public static final int PRIORITY_NORMAL = 0;
public static final int PRIORITY_HIGH = 1;
public DebugContext( String url, Integer key, RUIDebugTarget debugTarget, String launchConfigName )
{
super( url, key, new DebugContentProvider() );
this.debugTarget = debugTarget;
this.eventQueue = new ArrayList<String>();
this.launchConfigName = launchConfigName;
}
public RUIDebugTarget getDebugTarget()
{
return debugTarget;
}
public void clear()
{
debugTarget = null;
eventQueue = null;
contentProvider = null;
}
public String getLaunchConfigName()
{
return launchConfigName;
}
public void addEvent( String event, int priority )
{
if ( eventQueue != null )
{
synchronized ( eventQueue )
{
switch ( priority )
{
case PRIORITY_HIGH:
eventQueue.add( 0, event );
break;
case PRIORITY_NORMAL:
default:
eventQueue.add( event );
break;
}
}
}
}
private String getNextEvent()
{
if ( eventQueue != null )
{
synchronized ( eventQueue )
{
if ( eventQueue.size() == 0 )
{
return null;
}
return (String)eventQueue.remove( 0 );
}
}
return null;
}
public String waitForEvent()
{
long timeOut = 5000;
String eventToSend = getNextEvent();
while ( eventToSend == null && timeOut > 0 )
{
timeOut -= 100;
try
{
Thread.sleep( 100 );
}
catch ( Exception e )
{
}
eventToSend = getNextEvent();
}
// Either we found an event or time's up.
return eventToSend;
}
public void addBreakpoint( String file, String line, String enabled, boolean isSingleUse )
{
if ( isSingleUse )
{
addEvent( "egl.addSingleUseBreakpoint(\"" + file + "\", " + line + ")", PRIORITY_NORMAL ); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
else
{
addEvent( "egl.addBreakpoint(\"" + file + "\", " + line + ", " + enabled + ")", PRIORITY_NORMAL ); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
}
public void changeBreakpoint( String file, String oldline, String enabled )
{
addEvent( "egl.changeBreakpoint(\"" + file + "\", " + oldline + ", " + enabled + ")", PRIORITY_NORMAL ); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
public void removeBreakpoint( String file, String line )
{
addEvent( "egl.removeBreakpoint(\"" + file + "\", " + line + ")", PRIORITY_NORMAL ); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
public void breakpointManagerChanged( String enabled )
{
addEvent( "egl.breakpointManagerChanged(" + enabled + ")", PRIORITY_NORMAL ); //$NON-NLS-1$ //$NON-NLS-2$
}
public void disconnectDebugger()
{
addEvent( "egl.disconnectDebugger()", PRIORITY_HIGH ); //$NON-NLS-1$
}
@Override
public void handleEvent( Event event )
{
String url = event.url;
try
{
if ( url.indexOf( "___getevent" ) >= 0 ) //$NON-NLS-1$
{
getDebugEvent( event );
}
else if ( url.indexOf( "___atLine" ) != -1 ) //$NON-NLS-1$
{
atLine( event );
}
else if ( url.indexOf( "___getUserDebugRequest" ) != -1 ) //$NON-NLS-1$
{
getUserDebugRequest( event.ps );
}
else if ( url.indexOf( "___debugStack" ) != -1 ) //$NON-NLS-1$
{
String stackStr = (String)event.arguments.get( "stack" ); //$NON-NLS-1$
if ( stackStr == null )
{
stackStr = event.xmlRequest.getContent().substring( "stack=".length() ); //$NON-NLS-1$
stackStr = stackStr.replaceAll( " ", " " ); //$NON-NLS-1$ //$NON-NLS-2$
int indexOfAmp = stackStr.indexOf( '&' );
if ( indexOfAmp != -1 )
{
stackStr = stackStr.substring( 0, indexOfAmp );
}
}
debugStack( event.ps, stackStr );
}
else if ( url.indexOf( "___debugResume" ) != -1 ) //$NON-NLS-1$
{
debugResume( event.ps, event.arguments );
}
else if ( url.indexOf( "___varValue" ) != -1 ) //$NON-NLS-1$
{
// The variable value is always sent as content
varValue( event.ps, event.xmlRequest.getContentArguments() );
}
else if ( url.indexOf( "___varVariables" ) != -1 ) //$NON-NLS-1$
{
// The variable value is always sent as content
varVariables( event.ps, (String)event.xmlRequest.getContentArguments().get( "variables" ) ); //$NON-NLS-1$
}
else if ( url.indexOf( "___varSetValue" ) != -1 ) //$NON-NLS-1$
{
// The variable value is always sent as content
varSetValue( event.ps, event.xmlRequest.getContentArguments() );
}
else if ( url.indexOf( "___debugTerminate" ) != -1 ) //$NON-NLS-1$
{
String msg = debugTerminate();
event.ps.print( EvServer.getInstance().getGoodResponseHeader( "", "text/html", false ) ); //$NON-NLS-1$ //$NON-NLS-2$
try
{
event.ps.write( msg.getBytes( "UTF-8" ) ); //$NON-NLS-1$
}
catch ( UnsupportedEncodingException uee )
{
event.ps.write( msg.getBytes() );
}
}
else if ( url.indexOf( "___windowClosed" ) != -1 ) //$NON-NLS-1$
{
browserWindowClosed();
}
else if ( url.indexOf( "___getExistingBreakpoints" ) != -1 ) //$NON-NLS-1$
{
sendBreakpoints( event.ps );
}
else if ( url.indexOf( "___localeSettings" ) != -1 ) //$NON-NLS-1$
{
setDebugSessionLocale( event.ps, event.arguments );
}
else if ( url.indexOf( "___getBreakpointManagerState" ) != -1 ) //$NON-NLS-1$
{
getBreakpointManagerState( event.ps );
}
else if ( url.indexOf( "__getversion" ) != -1 ) //$NON-NLS-1$
{
EvServer.getInstance().sendVersion( event.ps );
}
else if ( url.indexOf( "___traceEvents" ) != -1 ) //$NON-NLS-1$
{
event.ps.print( EvServer.getInstance().getGoodResponseHeader( "", EvServer.getInstance().getContentType( "" ), false ) ); //$NON-NLS-1$ //$NON-NLS-2$
event.ps.print( "OK" ); //$NON-NLS-1$
event.ps.flush();
}
else if ( url.indexOf( ".." ) == -1 ) //$NON-NLS-1$
{ // no hacking this server, please
EvServer.getInstance().loadFile( url, event.key, event.ps );
}
else
{
EvServer.getInstance().fail( event.ps );
}
}
catch ( Exception ex )
{
ex.printStackTrace();
}
catch ( TokenMgrError ex )
{
ex.printStackTrace();
}
finally
{
if ( url.indexOf( "___getevent" ) == -1 && url.indexOf( "___atLine" ) == -1 ) //$NON-NLS-1$ //$NON-NLS-2$
{
event.ps.close();
try
{
event.socket.close();
}
catch ( IOException ex )
{
}
}
}
}
private void getDebugEvent( final Event event )
{
// Run in a new thread so we don't block other browser requests while waiting for an event.
new Thread() {
public void run()
{
String eventToSend = waitForEvent();
event.ps.print( EvServer.getInstance().getGoodResponseHeader( "", EvServer.getInstance().getContentType( "" ), false ) ); //$NON-NLS-1$ //$NON-NLS-2$
event.ps.print( eventToSend == null
? "" //$NON-NLS-1$
: eventToSend );
event.ps.close();
try
{
event.socket.close();
}
catch ( IOException ex )
{
}
}
}.start();
}
private void setDebugSessionLocale( PrintStream ps, Map args )
{
if ( debugTarget != null )
{
debugTarget.setLocaleInfo( args );
}
ps.print( EvServer.getInstance().getGoodResponseHeader( "", "text/html", false ) ); //$NON-NLS-1$ //$NON-NLS-2$
}
/**
* The browser occasionally polls for any user requests. This way requests like terminate and suspend don't have to wait for the function stack to
* complete before the getEvent response can be invoked.
*/
private void getUserDebugRequest( PrintStream ps )
{
ps.print( EvServer.getInstance().getGoodResponseHeader( "", "text/html", false ) ); //$NON-NLS-1$ //$NON-NLS-2$
if ( debugTarget != null )
{
if ( debugTarget.isTerminating() )
{
ps.print( "terminate" ); //$NON-NLS-1$
}
else if ( debugTarget.isSuspending() )
{
ps.print( "suspend" ); //$NON-NLS-1$
}
else
{
ps.print( "" ); //$NON-NLS-1$
}
}
else
{
ps.print( "" ); //$NON-NLS-1$
}
}
private void sendBreakpoints( PrintStream ps ) throws CoreException
{
StringBuffer sb = new StringBuffer();
if ( debugTarget != null )
{
IBreakpoint[] breakpoints = DebugPlugin.getDefault().getBreakpointManager()
.getBreakpoints( IEGLDebugCoreConstants.EGL_JAVA_MODEL_PRESENTATION_ID );
for ( int i = 0; i < breakpoints.length; i++ )
{
IBreakpoint breakpoint = breakpoints[ i ];
if ( debugTarget.supportsBreakpoint( breakpoint ) )
{
try
{
String relativePath = RUIDebugUtil.encodeValue( BreakpointUtils.getRelativeBreakpointPath( breakpoint ) );
if ( relativePath != null )
{
sb.append( relativePath );
sb.append( "," ); //$NON-NLS-1$
sb.append( Integer.toString( breakpoint.getMarker().getAttribute( IMarker.LINE_NUMBER, -1 ) ) );
sb.append( "," ); //$NON-NLS-1$
sb.append( Boolean.toString( breakpoint.isEnabled() ) );
if ( i != breakpoints.length - 1 )
{
sb.append( "," ); //$NON-NLS-1$
}
}
}
catch ( DebugException e )
{
}
catch ( EGLModelException e )
{
}
}
}
}
ps.print( EvServer.getInstance().getGoodResponseHeader( "", "text/html", false ) ); //$NON-NLS-1$ //$NON-NLS-2$
ps.print( sb.toString() );
}
private void getBreakpointManagerState( PrintStream ps ) throws CoreException
{
ps.print( EvServer.getInstance().getGoodResponseHeader( "", "text/html", false ) ); //$NON-NLS-1$ //$NON-NLS-2$
ps.print( DebugPlugin.getDefault().getBreakpointManager().isEnabled() );
}
private void browserWindowClosed()
{
// Terminate any debug sessions.
if ( debugTarget != null )
{
debugTarget.windowClosed();
}
debugTerminationCleanup();
}
/**
* @return a message to report to the user, or null.
*/
private String debugTerminate()
{
if ( debugTarget != null )
{
debugTarget.terminated();
return RUIDebugMessages.rui_debug_terminated_msg;
}
else
{
return RUIDebugMessages.rui_debug_refreshed_msg;
}
}
public void debugTerminationCleanup()
{
// Wait a couple seconds and check if the debug session has been
// terminated. If not, issue a warning and kill it.
if ( debugTarget != null )
{
// if already terminated (e.g. browser window closed) then don't spin off a new thread.
if ( debugTarget.isTerminated() )
{
RUIDebugContextResolver.getInstance().removeContext( this );
}
else
{
new Thread() {
public void run()
{
try
{
Thread.sleep( 2000 );
}
catch ( InterruptedException e )
{
}
Display.getDefault().asyncExec( new Runnable() {
public void run()
{
if ( !debugTarget.isTerminated() )
{
// TODO put the app's name in this message.
MessageDialog.openInformation( Display.getDefault().getActiveShell(),
RUIDebugMessages.DEBUG_REMOTECLIENTNOTRESPONDING_TITLE,
RUIDebugMessages.DEBUG_REMOTECLIENTNOTRESPONDING_MSG );
debugTerminate();
}
RUIDebugContextResolver.getInstance().removeContext( DebugContext.this );
}
} );
}
}.start();
}
}
}
private void debugStack( PrintStream ps, String stackStr )
{
if ( debugTarget != null )
{
debugTarget.parseStack( stackStr );
}
ps.print( EvServer.getInstance().getGoodResponseHeader( "", "text/html", false ) ); //$NON-NLS-1$ //$NON-NLS-2$
}
private void debugResume( PrintStream ps, Map args )
{
if ( debugTarget != null )
{
debugTarget.resume( (String)args.get( "resumeReason" ) ); //$NON-NLS-1$
}
ps.print( EvServer.getInstance().getGoodResponseHeader( "", "text/html", false ) ); //$NON-NLS-1$ //$NON-NLS-2$
}
private void atLine( final Event event )
{
// Run in a new thread so we don't block other browser requests while waiting
// for an event.
new Thread() {
public void run()
{
if ( debugTarget == null )
{
event.ps.print( EvServer.getInstance().getGoodResponseHeader( "", "text/html", false ) ); //$NON-NLS-1$ //$NON-NLS-2$
event.ps.print( "disconnect" ); //$NON-NLS-1$
}
else
{
/*
* If we don't have a stack, ask the runtime for one before we tell the debug target to suspend. The runtime will invoke
* __debugStack and then __atLine again to get back here.
*/
if ( debugTarget.needsNewStack() )
{
event.ps.print( EvServer.getInstance().getGoodResponseHeader( "stack", "text/html", false ) ); //$NON-NLS-1$ //$NON-NLS-2$
event.ps.print( "stack" ); //$NON-NLS-1$
}
else
{
String response = debugTarget.handleAtLine( event.arguments );
// Send the command back to be processed in function response() in egl.atLine
event.ps.print( EvServer.getInstance().getGoodResponseHeader( "", "text/html", false ) ); //$NON-NLS-1$ //$NON-NLS-2$
event.ps.print( response == null
? "" //$NON-NLS-1$
: response );
}
}
event.ps.close();
try
{
event.socket.close();
}
catch ( IOException ex )
{
}
}
}.start();
}
private void varValue( PrintStream ps, Map args )
{
if ( debugTarget != null )
{
debugTarget.varValue( args );
}
ps.print( EvServer.getInstance().getGoodResponseHeader( "", "text/html", false ) ); //$NON-NLS-1$ //$NON-NLS-2$
}
private void varVariables( PrintStream ps, String value )
{
if ( debugTarget != null )
{
debugTarget.varVariables( value );
}
ps.print( EvServer.getInstance().getGoodResponseHeader( "", "text/html", false ) ); //$NON-NLS-1$ //$NON-NLS-2$
}
private void varSetValue( PrintStream ps, Map args )
{
if ( debugTarget != null )
{
debugTarget.varSetValue( args );
}
ps.print( EvServer.getInstance().getGoodResponseHeader( "", "text/html", false ) ); //$NON-NLS-1$ //$NON-NLS-2$
}
@Override
public boolean useTestServer()
{
return true;
}
}