/******************************************************************************* * Copyright (c) 2005 - 2007 committers of openArchitectureWare 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: * committers of openArchitectureWare - initial API and implementation *******************************************************************************/ package org.eclipse.emf.mwe.internal.core.debug.processing; import static org.eclipse.emf.mwe.core.debug.processing.EventHandler.END_FRAME; import static org.eclipse.emf.mwe.core.debug.processing.EventHandler.NORMAL_FRAME; import static org.eclipse.emf.mwe.internal.core.debug.processing.ProcessHandler.INTERRUPT; import static org.eclipse.emf.mwe.internal.core.debug.processing.ProcessHandler.POP; import static org.eclipse.emf.mwe.internal.core.debug.processing.ProcessHandler.PUSH; import static org.eclipse.emf.mwe.internal.core.debug.processing.ProcessHandler.SHALL_HANDLE; import static org.eclipse.emf.mwe.internal.core.debug.processing.ProcessHandler.SUSPEND; import java.io.IOException; import java.net.ConnectException; import java.util.HashSet; import java.util.Set; import org.eclipse.emf.common.util.Diagnostic; import org.eclipse.emf.mwe.core.debug.processing.ElementAdapter; import org.eclipse.emf.mwe.core.debug.processing.EventHandler; import org.eclipse.emf.mwe.core.monitor.ProgressMonitor; import org.eclipse.emf.mwe.internal.core.debug.communication.Connection; /** * The heart of the debug process on the runtime side. It has callback methods that are called by the syntax element * implementations before and after a process step.<br> * It works closely together with handlers and adapters that must be registered at startup. */ public class DebugMonitor implements ProgressMonitor { private static final int STARTED = 1; private static final int PRE_TASK = 2; private static final int POST_TASK = 3; private static final int SUSPENDED = 4; private static final int RESUMED = 5; private static final int TERMINATED = 6; private final Connection connection = new Connection(); private CommandListener commandListener; private final Set<ProcessHandler> processHandlers = new HashSet<ProcessHandler>(); private final Set<EventHandler> eventHandlers = new HashSet<EventHandler>(); private final Set<ElementAdapter> elementAdapters = new HashSet<ElementAdapter>(); private boolean missingAdapterReported; private Object context; // ------------------------------------------------------------------------- /** * open the connection to a debug server framework (e.g. eclipse) and instantiate and start the * RuntimeHandlerManager listener. * * @param args * arg[1] must be the port to be connected with * @throws IOException */ public void init(final String[] args) throws IOException { // args[0] is the class name final int port = findPort(args); init(port); } public void init(int port) throws IOException { try { connection.connect(port); } catch (final ConnectException e) { throw new IOException("Couldn't establish connection to Debugger on port " + port); } try { final RuntimeHandlerManager handler = new RuntimeHandlerManager(this); handler.setConnection(connection); handler.startListener(); } catch (final Exception e) { connection.close(); if (e instanceof RuntimeException) { throw (RuntimeException) e; } throw (IOException) e; } } /** * Closes the connection. This releases the threads used for debugging and execution and releases memory. */ public void uninit() { connection.close(); } private int findPort(final String[] args) { for (final String string : args) { if (string.startsWith("port=")) { return Integer.parseInt(string.substring(5)); } } return 0; } // ------------------------------------------------------------------------- public void setCommandListener(final CommandListener commandListener) { this.commandListener = commandListener; } public void addProcessHandler(final ProcessHandler handler) { processHandlers.add(handler); } public void addEventHandler(final EventHandler handler) { eventHandlers.add(handler); } // ------------------------------------------------------------------------- public void addAdapter(final ElementAdapter adapter) { elementAdapters.add(adapter); } public ElementAdapter getAdapter(final Object element) { for (final ElementAdapter adapter : elementAdapters) { if (adapter.canHandle(element)) { return adapter; } } if (!missingAdapterReported) { System.out.println("Warning: Element can't be debugged.\nDidn't find an adapter for element of type " + element.getClass().getSimpleName()); missingAdapterReported = true; } return null; } // ------------------------------------- methods called from the mwe process /** * fire the STARTED event to the registered event handlers * * @see org.eclipse.emf.mwe.core.monitor.ProgressMonitor#started(java.lang.Object, java.lang.Object) */ public void started(final Object element, final Object context) { fireEvent(STARTED); } /** * the main method to manipulate the runtime process for debugging.<br> * In case a suspension is requested it stops the process and waits for the next user command. * * @see org.eclipse.emf.mwe.core.monitor.ProgressMonitor#preTask(java.lang.Object, java.lang.Object) */ public void preTask(final Object element, final Object context) { this.context = context; final ElementAdapter adapter = getAdapter(element); if (adapter == null) { return; } adapter.setContext(context); if (!ask(SHALL_HANDLE, element, PUSH)) { return; } fireEvent(PRE_TASK, element, NORMAL_FRAME); checkInterrupt(); try { if (ask(SUSPEND, element, NORMAL_FRAME)) { fireEvent(SUSPENDED); commandListener.listenCommand(); checkInterrupt(); fireEvent(RESUMED); } } catch (final IOException e) { throw new DebuggerInterruptedException("User interrupt"); } } /** * inform the handlers about the finalization of a process step.<br> * In case the process may suspend at the end of a syntax element it does so if requested. * * @see org.eclipse.emf.mwe.core.monitor.ProgressMonitor#postTask(java.lang.Object, java.lang.Object) */ public void postTask(final Object element, final Object context) { this.context = context; final ElementAdapter adapter = getAdapter(element); if (adapter == null) { return; } adapter.setContext(context); if (!ask(SHALL_HANDLE, element, POP)) { return; } try { if (adapter.isSurroundingElement(element)) { if (ask(SUSPEND, element, END_FRAME)) { // if we are at the end of a "subroutine" and going to stop at the next lower level task // anyway // we add another "frame" here to stop again at the closing syntax element // this enables us to check variable values before closing the frame fireEvent(PRE_TASK, element, END_FRAME); fireEvent(SUSPENDED); commandListener.listenCommand(); fireEvent(RESUMED); fireEvent(POST_TASK); } } fireEvent(POST_TASK); } catch (final IOException e) { throw new DebuggerInterruptedException(e); } } /** * fire the finish events to the registered event handlers * * @see org.eclipse.emf.mwe.core.monitor.ProgressMonitor#finished(java.lang.Object, java.lang.Object) */ public void finished(final Object element, final Object context) { getAdapter(element).setContext(context); fireEvent(PRE_TASK, element, END_FRAME); fireEvent(SUSPENDED); fireEvent(POST_TASK); fireEvent(TERMINATED); } // ------------------------------------------------------------------------- private void checkInterrupt() { if (ask(INTERRUPT, null, 0)) { // we throw an exception since the preTask() method is called // iteratively and we want to come back to the most outer loop throw new DebuggerInterruptedException("User interrupt"); } } // ------------------------------------------------------------------------- private void fireEvent(final int event) { fireEvent(event, null, 0); } private void fireEvent(final int event, final Object element, final int state) { for (final EventHandler handler : eventHandlers) { try { if (event == PRE_TASK) { handler.preTask(element, context, state); } else if (event == POST_TASK) { handler.postTask(context); } else if (event == STARTED) { handler.started(); } else if (event == SUSPENDED) { handler.suspended(); } else if (event == RESUMED) { handler.resumed(); } else if (event == TERMINATED) { handler.terminated(); } } catch (final IOException e) { throw new DebuggerInterruptedException(e); } } } private boolean ask(final int event, final Object element, final int state) { boolean result = false; ProcessHandler lastHandler = null; for (final ProcessHandler handler : processHandlers) { if (handler.isLastCall()) { lastHandler = handler; } else { result = ask(event, element, state, result, handler); } } if (lastHandler != null) { result = ask(event, element, state, result, lastHandler); } return result; } private boolean ask(final int event, final Object element, final int state, final boolean lastState, final ProcessHandler handler) { boolean result = lastState; if (event == SHALL_HANDLE) { result = handler.shallHandle(result, element, state); } else if (event == SUSPEND) { result = handler.shallSuspend(result, element, state); } else if (event == INTERRUPT) { result = handler.shallInterrupt(result); } return result; } // ************************** unused Progress Monitor implementation methods public void beginTask(final String name, final int totalWork) { // } public void done() { // } public void internalWorked(final double work) { // } public boolean isCanceled() { return false; } public void setCanceled(final boolean value) { // } public void setTaskName(final String name) { // } public void subTask(final String name) { // } public void worked(final int work) { // } public void clearBlocked() { // } public void setBlocked(final Diagnostic reason) { // } }