| /******************************************************************************* |
| * Copyright (c) 2009, 2018 R.Dvorak and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v2.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v20.html |
| * |
| * Contributors: |
| * Radek Dvorak - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.m2m.qvt.oml.debug.core; |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.core.resources.IMarker; |
| import org.eclipse.core.resources.IMarkerDelta; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.debug.core.DebugEvent; |
| import org.eclipse.debug.core.DebugException; |
| import org.eclipse.debug.core.DebugPlugin; |
| import org.eclipse.debug.core.IBreakpointManager; |
| import org.eclipse.debug.core.IBreakpointManagerListener; |
| import org.eclipse.debug.core.IDebugEventSetListener; |
| import org.eclipse.debug.core.ILaunch; |
| import org.eclipse.debug.core.IStatusHandler; |
| import org.eclipse.debug.core.model.IBreakpoint; |
| 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.IThread; |
| import org.eclipse.debug.core.model.IValue; |
| import org.eclipse.emf.common.util.URI; |
| import org.eclipse.m2m.internal.qvt.oml.ast.env.QvtOperationalEvaluationEnv; |
| import org.eclipse.m2m.qvt.oml.debug.core.vm.IQVTOVirtualMachineShell; |
| import org.eclipse.m2m.qvt.oml.debug.core.vm.QVTOVirtualMachine; |
| import org.eclipse.m2m.qvt.oml.debug.core.vm.VMEventListener; |
| import org.eclipse.m2m.qvt.oml.debug.core.vm.protocol.NewBreakpointData; |
| import org.eclipse.m2m.qvt.oml.debug.core.vm.protocol.VMBreakpointRequest; |
| import org.eclipse.m2m.qvt.oml.debug.core.vm.protocol.VMBreakpointResponse; |
| import org.eclipse.m2m.qvt.oml.debug.core.vm.protocol.VMDisconnectEvent; |
| import org.eclipse.m2m.qvt.oml.debug.core.vm.protocol.VMEvent; |
| import org.eclipse.m2m.qvt.oml.debug.core.vm.protocol.VMRequest; |
| import org.eclipse.m2m.qvt.oml.debug.core.vm.protocol.VMResponse; |
| import org.eclipse.m2m.qvt.oml.debug.core.vm.protocol.VMResumeEvent; |
| import org.eclipse.m2m.qvt.oml.debug.core.vm.protocol.VMResumeRequest; |
| import org.eclipse.m2m.qvt.oml.debug.core.vm.protocol.VMStartEvent; |
| import org.eclipse.m2m.qvt.oml.debug.core.vm.protocol.VMStartRequest; |
| import org.eclipse.m2m.qvt.oml.debug.core.vm.protocol.VMSuspendEvent; |
| import org.eclipse.m2m.qvt.oml.debug.core.vm.protocol.VMSuspendRequest; |
| import org.eclipse.m2m.qvt.oml.debug.core.vm.protocol.VMTerminateEvent; |
| import org.eclipse.m2m.qvt.oml.debug.core.vm.protocol.VMTerminateRequest; |
| |
| public class QVTODebugTarget extends QVTODebugElement implements IQVTODebugTarget, IDebugEventSetListener, IBreakpointManagerListener { |
| |
| private final Map<Long, QVTOBreakpoint> fID2Breakpoint = new HashMap<Long, QVTOBreakpoint>(); |
| |
| private final ILaunch fLaunch; |
| |
| private final IProcess fProcess; |
| |
| private QVTOThread fMainThread; |
| |
| private String fMainModuleName; |
| |
| private boolean fIsStarting; |
| private boolean fIsSuspended = false; |
| |
| private final IQVTOVirtualMachineShell fVM; |
| private final List<VMEventListener> fEventListener = new LinkedList<VMEventListener>(); |
| |
| private final Object fVMStartMonitor = new Object(); |
| |
| public QVTODebugTarget(IProcess process, IQVTOVirtualMachineShell vm) { |
| super(null); |
| |
| fLaunch = process.getLaunch(); |
| fProcess = process; |
| fVM = vm; |
| fIsStarting = true; |
| fEventListener.add(createVMEventListener()); |
| |
| EventDispatchJob dispatcher = new EventDispatchJob(); |
| Thread eventDispatherThread = new Thread(dispatcher, "QVTO VM Event Dispatch"); //$NON-NLS-1$ |
| eventDispatherThread.setDaemon(true); |
| eventDispatherThread.start(); |
| |
| try { |
| // start transformation execution |
| sendRequest(new VMStartRequest()); |
| } catch (DebugException e) { |
| QVTODebugCore.log(e.getStatus()); |
| // FIXME - consult status handler to give UI feedback |
| return; |
| } |
| |
| joinStartOrTerminate(); |
| // Note: VM is still suspended and waiting for resume |
| // => do whatever initialization we need now |
| // install VM breakpoints |
| installVMBreakpoints(); |
| |
| DebugEvent createEvent = new DebugEvent(this, DebugEvent.CREATE); |
| createEvent.setData(new HashMap<Long, QVTOBreakpoint>(fID2Breakpoint)); |
| |
| fMainThread = new QVTOThread(this); |
| fLaunch.addDebugTarget(this); |
| System.setProperty(QVTODebugCore.DEBUGGER_ACTIVE_PROPERTY, Boolean.TRUE.toString()); |
| |
| try { |
| // wake up so far suspended VM |
| fVM.sendRequest(new VMResumeRequest(0)); |
| } catch (IOException e) { |
| QVTODebugCore.log(e); |
| } |
| |
| IBreakpointManager breakpointManager = DebugPlugin.getDefault().getBreakpointManager(); |
| breakpointManager.addBreakpointManagerListener(this); |
| breakpointManager.addBreakpointListener(this); |
| DebugPlugin.getDefault().addDebugEventListener(this); |
| |
| fireEvent(createEvent); |
| } |
| |
| protected URI computeBreakpointURI(URI sourceURI) { |
| return sourceURI; |
| } |
| |
| private void installVMBreakpoints() { |
| HashMap<Long, QVTOBreakpoint> installedBreakpoints = new HashMap<Long, QVTOBreakpoint>(); |
| List<NewBreakpointData> allBpData = new ArrayList<NewBreakpointData>(); |
| |
| for (QVTOBreakpoint qvtBp : QVTODebugCore.getQVTOBreakpoints(QVTOBreakpoint.class)) { |
| boolean enabled = false; |
| try { |
| enabled = qvtBp.isEnabled(); |
| } catch (CoreException e) { |
| QVTODebugCore.log(e.getStatus()); |
| } |
| |
| if (enabled) { |
| installedBreakpoints.put(new Long(((QVTOBreakpoint) qvtBp).getID()), qvtBp); |
| try { |
| NewBreakpointData data = qvtBp.createNewBreakpointData(); |
| data.targetURI = computeBreakpointURI(URI.createURI(data.targetURI, true)).toString(); |
| |
| allBpData.add(data); |
| } catch (CoreException e) { |
| QVTODebugCore.log(e.getStatus()); |
| } catch (Exception e) { |
| QVTODebugCore.log(QVTODebugUtil.createDebugError("Failed to install breakpoint", e)); |
| } |
| } |
| } |
| |
| if (!allBpData.isEmpty()) { |
| VMBreakpointRequest breakpointRequest = VMBreakpointRequest |
| .createAdd(allBpData |
| .toArray(new NewBreakpointData[allBpData.size()])); |
| try { |
| VMResponse response = fVM.sendRequest(breakpointRequest); |
| // |
| fID2Breakpoint.clear(); |
| if(response instanceof VMBreakpointResponse) { |
| VMBreakpointResponse bpResponse = (VMBreakpointResponse) response; |
| |
| for(long addedID : bpResponse.getAddedBreakpointsIDs()) { |
| Long key = new Long(addedID); |
| QVTOBreakpoint bp = installedBreakpoints.get(key); |
| if(bp != null) { |
| fID2Breakpoint.put(key, bp); |
| } |
| } |
| } |
| } catch (IOException e) { |
| QVTODebugCore.log(e); |
| } |
| } |
| } |
| |
| public Collection<? extends IBreakpoint> getInstalledBreakpoints() { |
| return Collections.unmodifiableCollection(fID2Breakpoint.values()); |
| } |
| |
| public VMResponse sendRequest(VMRequest request) throws DebugException { |
| try { |
| return fVM.sendRequest(request); |
| } catch (IOException e) { |
| throw new DebugException(QVTODebugUtil.createDebugError( |
| "Send debug request failed", e)); |
| } |
| } |
| |
| public synchronized boolean isSuspended() { |
| return !isTerminated() && fIsSuspended; |
| } |
| |
| @Override |
| public IDebugTarget getDebugTarget() { |
| return this; |
| } |
| |
| @Override |
| public ILaunch getLaunch() { |
| return fLaunch; |
| } |
| |
| public IQVTOVirtualMachineShell getVM() { |
| return fVM; |
| } |
| |
| public IProcess getProcess() { |
| IProcess[] processes = getLaunch().getProcesses(); |
| if (processes != null && processes.length > 0) { |
| return processes[0]; |
| } |
| |
| return null; |
| } |
| |
| public boolean hasThreads() throws DebugException { |
| return !isTerminated(); |
| } |
| |
| public IThread[] getThreads() throws DebugException { |
| return (fMainThread != null) ? new IThread[] { fMainThread } |
| : new IThread[0]; |
| } |
| |
| public String getName() throws DebugException { |
| return "QVTO Debug target"; |
| } |
| |
| public boolean supportsBreakpoint(IBreakpoint breakpoint) { |
| return breakpoint.getModelIdentifier().equals(getModelIdentifier()); |
| } |
| |
| public boolean canTerminate() { |
| return !isTerminated(); |
| } |
| |
| public boolean isTerminated() { |
| return fVM.isTerminated(); |
| } |
| |
| public void terminate() throws DebugException { |
| sendRequest(new VMTerminateRequest()); |
| } |
| |
| protected void started(String mainModuleName) { |
| setMainModuleName(mainModuleName); |
| setStarting(false); |
| } |
| |
| synchronized protected void setMainModuleName(String mainModuleName) { |
| fMainModuleName = mainModuleName; |
| } |
| |
| synchronized public String getMainModuleName() { |
| return fMainModuleName; |
| } |
| |
| protected void terminated() { |
| QVTODebugCore.TRACE.trace(DebugOptions.TARGET, "Debug target terminated"); //$NON-NLS-1$ |
| System.setProperty(QVTODebugCore.DEBUGGER_ACTIVE_PROPERTY, Boolean.FALSE.toString()); |
| |
| setStarting(false); |
| |
| fMainThread = null; |
| |
| DebugPlugin debugPlugin = DebugPlugin.getDefault(); |
| if (debugPlugin != null) { |
| IBreakpointManager breakpointManager = debugPlugin |
| .getBreakpointManager(); |
| breakpointManager.removeBreakpointListener(this); |
| breakpointManager.removeBreakpointManagerListener(this); |
| |
| debugPlugin.removeDebugEventListener(this); |
| } |
| |
| fID2Breakpoint.clear(); |
| |
| fireTerminateEvent(); |
| if(fProcess instanceof QVTOVirtualProcess) { |
| QVTOVirtualProcess vp = (QVTOVirtualProcess) fProcess; |
| vp.terminated(); |
| } |
| } |
| |
| public boolean canResume() { |
| return !isTerminated() && isSuspended(); |
| } |
| |
| public boolean canSuspend() { |
| return !isTerminated() && !isSuspended(); |
| } |
| |
| public void resume() throws DebugException { |
| sendRequest(new VMResumeRequest(DebugEvent.UNSPECIFIED)); |
| } |
| |
| public void suspend() throws DebugException { |
| sendRequest(new VMSuspendRequest(DebugEvent.UNSPECIFIED)); |
| } |
| |
| public void breakpointAdded(IBreakpoint breakpoint) { |
| if (breakpoint instanceof QVTOBreakpoint == false |
| || !DebugPlugin.getDefault().getBreakpointManager().isEnabled()) { |
| return; |
| } |
| |
| QVTOBreakpoint qvtBreakpoint = (QVTOBreakpoint) breakpoint; |
| try { |
| NewBreakpointData bpData = qvtBreakpoint.createNewBreakpointData(); |
| VMBreakpointRequest addBreakpointRequest = VMBreakpointRequest |
| .createAdd(bpData); |
| |
| VMResponse response = sendRequest(addBreakpointRequest); |
| if(response instanceof VMBreakpointResponse) { |
| VMBreakpointResponse bpResponse = (VMBreakpointResponse) response; |
| long[] addedIDs = bpResponse.getAddedBreakpointsIDs(); |
| if(addedIDs.length > 0) { |
| fID2Breakpoint.put(new Long(addedIDs[0]), qvtBreakpoint); |
| } |
| } |
| } catch (CoreException e) { |
| QVTODebugCore.log(e.getStatus()); |
| } |
| } |
| |
| public void breakpointChanged(IBreakpoint breakpoint, IMarkerDelta delta) { |
| if (breakpoint instanceof QVTOBreakpoint == false |
| || !DebugPlugin.getDefault().getBreakpointManager().isEnabled()) { |
| return; |
| } |
| |
| boolean nowEnabled = false; |
| try { |
| nowEnabled = breakpoint.isEnabled(); |
| } catch (CoreException e1) { |
| // do nothing |
| } |
| boolean beforeEnabled = delta.getAttribute(IBreakpoint.ENABLED, false); |
| |
| VMBreakpointRequest changeRequest = null; |
| try { |
| QVTOBreakpoint qvtBreakpoint = (QVTOBreakpoint) breakpoint; |
| if (nowEnabled && !beforeEnabled) { |
| // just to be added to VM |
| changeRequest = VMBreakpointRequest |
| .createAdd(new NewBreakpointData[] { qvtBreakpoint |
| .createNewBreakpointData() }); |
| } else if (!nowEnabled && beforeEnabled) { |
| // just to be removed from VM |
| changeRequest = VMBreakpointRequest.createRemove(qvtBreakpoint |
| .getID()); |
| } else { |
| // modify existing data |
| changeRequest = VMBreakpointRequest.createChange(qvtBreakpoint |
| .getID(), qvtBreakpoint.createBreakpointData()); |
| } |
| |
| } catch (CoreException e) { |
| QVTODebugCore.log(e); |
| } |
| |
| if (changeRequest != null) { |
| try { |
| fVM.sendRequest(changeRequest); |
| } catch (IOException e) { |
| QVTODebugCore.log(e); |
| } |
| } |
| } |
| |
| public void breakpointRemoved(IBreakpoint breakpoint, IMarkerDelta delta) { |
| if (breakpoint instanceof QVTOBreakpoint) { |
| if (delta == null) { |
| IMarker marker = breakpoint.getMarker(); |
| if (marker.exists()) { |
| try { |
| marker.delete(); |
| } catch (CoreException e) { |
| // TODO Auto-generated catch block |
| e.printStackTrace(); |
| } |
| } |
| } |
| QVTOBreakpoint qvtBreakpoint = (QVTOBreakpoint) breakpoint; |
| fID2Breakpoint.remove(new Long(((QVTOBreakpoint) breakpoint) |
| .getID())); |
| |
| VMBreakpointRequest removeRequest = VMBreakpointRequest |
| .createRemove(qvtBreakpoint.getID()); |
| try { |
| fVM.sendRequest(removeRequest); |
| } catch (IOException e) { |
| QVTODebugCore.log(e); |
| } |
| } |
| } |
| |
| public boolean canDisconnect() { |
| return false; |
| } |
| |
| public void disconnect() throws DebugException { |
| } |
| |
| public boolean isDisconnected() { |
| return false; |
| } |
| |
| public boolean supportsStorageRetrieval() { |
| return false; |
| } |
| |
| public IMemoryBlock getMemoryBlock(long startAddress, long length) throws DebugException { |
| return null; |
| } |
| |
| public void handleDebugEvents(DebugEvent[] events) { |
| for (int i = 0; i < events.length; i++) { |
| DebugEvent event = events[i]; |
| |
| if (event.getKind() == DebugEvent.TERMINATE) { |
| // respond |
| if ((fMainThread != null && event.getSource() == fMainThread) |
| || (event.getSource() == fProcess)) { |
| if(!isTerminated()) { |
| terminated(); |
| } |
| } |
| } |
| } |
| } |
| |
| public void breakpointManagerEnablementChanged(boolean enabled) { |
| for (IBreakpoint breakpoint : QVTODebugCore |
| .getQVTOBreakpoints(IBreakpoint.class)) { |
| if (enabled) { |
| breakpointAdded(breakpoint); |
| } else { |
| breakpointRemoved(breakpoint, null); |
| } |
| } |
| } |
| |
| private void joinStartOrTerminate() { |
| synchronized (fVMStartMonitor) { |
| while(fIsStarting) { |
| try { |
| // wait until we receive VM startup event |
| fVMStartMonitor.wait(); |
| } catch (InterruptedException e) { |
| Thread.interrupted(); |
| } |
| } |
| } |
| } |
| |
| |
| private void setStarting(boolean isStarting) { |
| synchronized (fVMStartMonitor) { |
| fIsStarting = isStarting; |
| fVMStartMonitor.notify(); |
| } |
| } |
| |
| private void handleBreakpointConditionError(VMSuspendEvent suspend) { |
| IStatus breakpointStatus = new BreakpointError(suspend |
| .getBreakpointID(), suspend.getReason(), |
| suspend.getReasonDetail()); |
| |
| IStatusHandler handler = DebugPlugin.getDefault().getStatusHandler(breakpointStatus); |
| if(handler != null) { |
| try { |
| handler.handleStatus(breakpointStatus, QVTODebugTarget.this); |
| } catch (CoreException e) { |
| QVTODebugCore.log(e.getStatus()); |
| } |
| } else { |
| // no custom handler found, at least log the status |
| QVTODebugCore.log(breakpointStatus); |
| } |
| } |
| |
| private VMEventListener createVMEventListener() { |
| return new VMEventListener() { |
| |
| public void handleEvent(VMEvent event) { |
| if (event instanceof VMResumeEvent) { |
| fIsSuspended = false; |
| fireResumeEvent(0); |
| } else if (event instanceof VMSuspendEvent) { |
| fIsSuspended = true; |
| |
| VMSuspendEvent suspend = (VMSuspendEvent) event; |
| fireSuspendEvent(suspend.detail); |
| |
| if (suspend.detail == VMSuspendEvent.BREAKPOINT_CONDITION_ERR) { |
| handleBreakpointConditionError(suspend); |
| } |
| |
| } else if (event instanceof VMTerminateEvent) { |
| fIsSuspended = false; |
| terminated(); |
| } else if (event instanceof VMDisconnectEvent) { |
| fIsSuspended = false; |
| terminated(); |
| } else if (event instanceof VMStartEvent) { |
| started(((VMStartEvent) event).mainModuleName); |
| } |
| } |
| |
| }; |
| } |
| |
| public void addVMEventListener(VMEventListener listener) { |
| if (listener == null) { |
| throw new IllegalArgumentException(); |
| } |
| |
| synchronized (fEventListener) { |
| fEventListener.add(listener); |
| } |
| } |
| |
| public boolean removeVMEventListener(VMEventListener listener) { |
| synchronized (fEventListener) { |
| return fEventListener.remove(listener); |
| } |
| } |
| |
| void handleVMEvent(VMEvent event) { |
| List<VMEventListener> listeners; |
| synchronized (fEventListener) { |
| listeners = new ArrayList<VMEventListener>(fEventListener); |
| } |
| |
| for (VMEventListener vmEventListener : listeners) { |
| try { |
| vmEventListener.handleEvent(event); |
| } catch (Exception e) { |
| QVTODebugCore.log(e); |
| } |
| } |
| } |
| |
| public IValue evaluate(String expressionText, long frameID) throws CoreException { |
| if (getVM() instanceof QVTOVirtualMachine) { |
| return ((QVTOVirtualMachine) getVM()).evaluate(expressionText, this, frameID); |
| } |
| return null; |
| } |
| |
| @Override |
| public Object getAdapter(Class adapter) { |
| if (QvtOperationalEvaluationEnv.class == adapter) { |
| if (getVM() instanceof QVTOVirtualMachine) { |
| return ((QVTOVirtualMachine) getVM()).getEvaluationEnv(); |
| } |
| } |
| return super.getAdapter(adapter); |
| } |
| |
| private class EventDispatchJob implements Runnable { |
| |
| EventDispatchJob() { |
| super(); |
| } |
| |
| public void run() { |
| while (!isTerminated()) { |
| VMEvent event; |
| try { |
| event = fVM.readVMEvent(); |
| } catch (IOException e) { |
| break; |
| } |
| |
| if (event != null) { |
| handleVMEvent(event); |
| } |
| } |
| |
| QVTODebugCore.TRACE.trace(DebugOptions.TARGET, |
| "Debug target VMEvent dispatcher shutdown"); //$NON-NLS-1$ |
| } |
| } |
| |
| } |