package xvrengine.debug;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import org.eclipse.core.resources.IMarkerDelta;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.debug.core.model.IDebugElement;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.core.model.IMemoryBlock;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.core.model.IStackFrame;
import org.eclipse.debug.core.model.IThread;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import org.xvr.xvrengine.perspective.XVRPerspectiveFactory;
import org.xvr.xvrengine.process.IProcessExitListener;
import org.xvr.xvrengine.process.ProcessExitHandler;
import org.xvr.xvrengine.util.XVRUtils;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class XVRDebugTarget extends XVRDebugElement implements IDebugTarget{
private static final int RETRY = 10;
private ILaunch launch;
private IProcess process;
// sockets to communicate with VM
private Socket eventSocket;
private Socket commandSocket;
private BufferedReader eventReader;
private PrintStream commandWriter;
private BufferedReader commandReader;
private VMThread mainThread;
private IThread[] threads;
private boolean suspended;
private String name;
private IStackFrame[] frames;
// event dispatch job
private EventDispatchJob eventDispatch;
class EventDispatchJob extends Job {
private int comPort;
private int evPort;
private boolean canceled;
public EventDispatchJob(int commandPort, int eventPort) {
super("XVR Event Dispatcher");
this.comPort = commandPort;
this.evPort = eventPort;
this.canceled = false;
setSystem(true);
}
@Override
protected void canceling() {
this.canceled = true;
}
@Override
protected IStatus run(IProgressMonitor monitor) {
//connects to the VirtualMachine
for(int i = 0; i < RETRY; i++){
if(monitor.isCanceled() || this.canceled)
return Status.OK_STATUS;
try {
if(commandSocket == null) commandSocket = new Socket("localhost", this.comPort);
commandWriter = new PrintStream(commandSocket.getOutputStream());
commandReader = new BufferedReader(new InputStreamReader(commandSocket.getInputStream()));
if(eventSocket == null) eventSocket = new Socket("localhost", this.evPort);
eventReader = new BufferedReader(new InputStreamReader(eventSocket.getInputStream()));
break;
}catch (IOException e) {
if(i == RETRY - 1){
XVRUtils.setDebugSession(false);
XVRUtils.displayWarning("Warning!\n" +
"Debugger connection to the XVR Virtual Machine has timed out. The debugger functionalities will be inactive for this session." +
"\n\nPlease note that debug for Full Screen is currently not supported.");
return Status.OK_STATUS;
}
else{
try {
Thread.sleep(5);
} catch (InterruptedException e1) {}
}
}
}
DebugPlugin.getDefault().getBreakpointManager().addBreakpointListener(XVRDebugTarget.this);
//Starts fetch/decode/execute cycles
String event = "";
while (!isTerminated() && event != null) {
if(monitor.isCanceled() || this.canceled)
return Status.OK_STATUS;
try {
event = eventReader.readLine();
System.out.println("Event reached: " + event);
if (event != null) {
if(event.startsWith("Breakpoint")){
breakpointHit(event);
}else if(event.startsWith("Resumed")){
resumed(event);
}else if(event.startsWith("Stopped")){
terminated();
break;
}else if(event.startsWith("Started")){
started();
}
}
} catch (IOException e) {
terminated();
}
}
return Status.OK_STATUS;
}
}
public XVRDebugTarget(ILaunch launch, IProcess process, int commandPort, int eventPort, ProcessExitHandler pExit) throws DebugException{
super(null);
pExit.addListener(new IProcessExitListener() {
@Override
public void processFinished() {
eventDispatch.cancel();
}
});
this.launch = launch;
this.process = process;
this.mainThread = new VMThread(XVRDebugTarget.this);
this.threads = new IThread[] {mainThread};
this.eventDispatch = new EventDispatchJob(commandPort, eventPort);
this.eventDispatch.schedule();
}
@Override
public String getName() throws DebugException {
if (name == null) {
name = "XVR Virtual Machine";
try {
name = "XVR Virtual Machine: " + getLaunch().getLaunchConfiguration().getAttribute(IXVRConstants.ATTR_XVR_MAIN, "bin file");
if(name.endsWith(".s3d")) name = name.substring(0, name.length() - 4) + ".bin";
} catch (CoreException e) {
}
}
return name;
}
@Override
public IProcess getProcess() {
return this.process;
}
@Override
public IThread[] getThreads() throws DebugException {
return threads;
}
@Override
public boolean hasThreads() throws DebugException {
return threads.length != 0;
}
@Override
public boolean supportsBreakpoint(IBreakpoint breakpoint) {
if(this.isTerminated()) return false;
if(!breakpoint.getModelIdentifier().equals(IXVRConstants.ID_XVR_DEBUG_MODEL)) return false;
return true;
}
@Override
public IDebugTarget getDebugTarget() {
return this;
}
@Override
public ILaunch getLaunch() {
return launch;
}
@Override
public String getModelIdentifier() {
return IXVRConstants.ID_XVR_DEBUG_MODEL;
}
@Override
public boolean canTerminate() {
return process.canTerminate();
}
@Override
public boolean isTerminated() {
return process.isTerminated();
}
@Override
public void terminate() throws DebugException {
sendRequest("exit");
}
@Override
public boolean canResume() {
return !isTerminated() && isSuspended();
}
@Override
public boolean canSuspend() {
return false; //return !isTerminated() && !isSuspended();
}
@Override
public boolean isSuspended() {
return suspended;
}
@Override
public void resume() throws DebugException {
sendRequest("continue");
}
@Override
public void suspend() throws DebugException {
// TODO Auto-generated method stub
}
@Override
public void breakpointAdded(IBreakpoint breakpoint) {
try {
if(supportsBreakpoint(breakpoint) && breakpoint.isEnabled()) addBreakpoint((XVRBreakpoint)breakpoint);
} catch (CoreException e) {
e.printStackTrace();
}
}
@Override
public void breakpointChanged(IBreakpoint breakpoint, IMarkerDelta delta) {
if (supportsBreakpoint(breakpoint)) {
try {
if (breakpoint.isEnabled()) {
breakpointAdded(breakpoint);
} else {
breakpointRemoved(breakpoint, null);
}
} catch (CoreException e) {
}
}
}
@Override
public void breakpointRemoved(IBreakpoint breakpoint, IMarkerDelta delta) {
try{
if(supportsBreakpoint(breakpoint) && breakpoint.isEnabled()) removeBreakpoint((XVRBreakpoint) breakpoint);
}catch(CoreException e){
e.printStackTrace();
}
}
@Override
public boolean canDisconnect() {
return false;
}
@Override
public void disconnect() throws DebugException {
}
@Override
public boolean isDisconnected() {
return false;
}
@Override
public IMemoryBlock getMemoryBlock(long startAddress, long length) throws DebugException {
return null;
}
@Override
public boolean supportsStorageRetrieval() {
return false;
}
public void stepInto() throws DebugException{
sendRequest("step");
}
public void stepOver() throws DebugException{
sendRequest("next");
}
public IStackFrame[] getStackFrames(){
return frames;
}
public IStackFrame[] updateStackFrames() throws DebugException {
synchronized(commandSocket){
if(commandSocket.isClosed()) return new IStackFrame[0];
commandWriter.println("bt");
commandWriter.flush();
try{
commandReader.readLine(); //read ok
String framesData = commandReader.readLine();
JSONObject answer = (JSONObject)JSONValue.parse(framesData);
if(answer == null) return new IStackFrame[0];
//System.out.println(answer.toJSONString());
JSONArray frames = (JSONArray)answer.get("stackframes");
IStackFrame[] theFrames = new IStackFrame[frames.size()];
try{
for(int i = 0; i < frames.size(); i++)
theFrames[i] = new XVRStackFrame(launch.getLaunchConfiguration(), mainThread, (JSONObject)frames.get(i), i);
} catch (CoreException e) {
abort("Unable to retrieve a stack frame", e);
}
return theFrames;
}catch(IOException e){
abort("Unable to retrieve stack frames", e);
}
}
return new IStackFrame[0];
}
private void started(){
XVRUtils.setDebugSession(true);
fireCreationEvent();
mainThread.fireCreationEvent();
installDeferredBreakpoints();
try {
resume();
} catch (DebugException e) {
e.printStackTrace();
}
}
private void installDeferredBreakpoints() {
IBreakpoint[] breakpoints = DebugPlugin.getDefault().getBreakpointManager().getBreakpoints();
for(int i = 0; i < breakpoints.length; i++)
if(supportsBreakpoint(breakpoints[i])) addBreakpoint((XVRBreakpoint)breakpoints[i]);
}
private void terminated(){
threads = new IThread[0];
suspended = false;
DebugPlugin.getDefault().getBreakpointManager().removeBreakpointListener(this);
mainThread.fireTerminateEvent();
fireTerminateEvent();
//XVRUtils.setDebugSession(false);
XVRUtils.switchPerspective(XVRPerspectiveFactory.getPerspectiveID());
}
private void resumed(String event){
Pattern pattern = Pattern.compile("Resumed by (.*)");
Matcher matcher = pattern.matcher(event);
if(matcher.matches()){
suspended = false;
if(matcher.group(1).startsWith("client")){
mainThread.fireResumeEvent(DebugEvent.CLIENT_REQUEST);
}else if(matcher.group(1).startsWith("step into")){
mainThread.setStepping(true);
mainThread.fireResumeEvent(DebugEvent.STEP_INTO);
}else{
mainThread.setStepping(true);
mainThread.fireResumeEvent(DebugEvent.STEP_OVER);
}
}
}
private void addBreakpoint(XVRBreakpoint breakpoint){
try {
IResource res = breakpoint.getMarker().getResource();
int line = breakpoint.getLineNumber();
String filename = null;
if(res.isLinked())
filename = XVRUtils.getResourceName(res);
else
filename = breakpoint.getMarker().getResource().getProjectRelativePath().toString();
if(filename == null){
XVRUtils.displayWarning("Unable to add breakpoint at:\nFile: " + res.getName() + "\nLine: " + line);
return;
}
filename = filename.replace("\\", "/");
sendRequest("break " + filename + ":" + line);
} catch (CoreException e) {
e.printStackTrace();
}
}
private void removeBreakpoint(XVRBreakpoint breakpoint){
try {
String filename = breakpoint.getMarker().getResource().getProjectRelativePath().toString();
int line = breakpoint.getLineNumber();
sendRequest("delete " + filename + ":" + line);
} catch (CoreException e) {
e.printStackTrace();
}
}
private void sendRequest(String request) throws DebugException {
synchronized (commandSocket) {
commandWriter.println(request);
commandWriter.flush();
try {
/*String line = */commandReader.readLine();
//System.out.println(line);
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void breakpointHit(String event){
try {
frames = updateStackFrames();
} catch (DebugException e) {
e.printStackTrace();
}
suspended = true;
if(mainThread.isStepping()){
mainThread.setStepping(false);
mainThread.fireSuspendEvent(DebugEvent.STEP_END);
}else mainThread.fireSuspendEvent(DebugEvent.BREAKPOINT);
}
private void abort(String message, Throwable e) throws DebugException {
throw new DebugException(new Status(IStatus.ERROR, IXVRConstants.ID_XVR_DEBUG_MODEL, 0, message, e));
}
public String evaluate(IDebugElement context, String expression) throws DebugException{
String evaluation = null;
changeFrame(context);
synchronized(commandSocket){
if(commandSocket.isClosed()) return null;
commandWriter.println("print " + expression);
commandWriter.flush();
try {
commandReader.readLine();
evaluation = commandReader.readLine();
} catch (IOException e) {
e.printStackTrace();
}
}
restoreFrame();
return evaluation;
}
private void changeFrame(IDebugElement context) throws DebugException{
if(context instanceof XVRStackFrame){
XVRStackFrame stackframe = (XVRStackFrame)context;
sendRequest("frame " + stackframe.getFrameID());
}
}
private void restoreFrame() throws DebugException{
sendRequest("frame 0");
}
}