/******************************************************************************* * Copyright (c) 2009 R.Dvorak 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: * 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$ } } }