/******************************************************************************* * Copyright (c) 2009-2012 CWI * 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: * * Jurgen J. Vinju - Jurgen.Vinju@cwi.nl - CWI * * Emilie Balland - (CWI) * * Arnold Lankamp - Arnold.Lankamp@cwi.nl * * Michael Steindorfer - Michael.Steindorfer@cwi.nl - CWI *******************************************************************************/ package org.rascalmpl.eclipse.debug.core.breakpoints; import static org.rascalmpl.debug.DebugMessageFactory.requestDeleteBreakpoint; import static org.rascalmpl.debug.DebugMessageFactory.requestSetBreakpoint; import java.net.URI; import java.net.URISyntaxException; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspaceRunnable; import org.eclipse.core.runtime.Assert; 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.debug.core.DebugException; import org.eclipse.debug.core.model.IBreakpoint; import org.eclipse.debug.core.model.IThread; import org.eclipse.debug.core.model.LineBreakpoint; import org.rascalmpl.debug.IRascalEventListener; import org.rascalmpl.debug.RascalEvent; import org.rascalmpl.eclipse.Activator; import org.rascalmpl.eclipse.IRascalResources; import org.rascalmpl.eclipse.debug.core.model.RascalDebugTarget; import org.rascalmpl.eclipse.debug.core.model.RascalThread; import org.rascalmpl.uri.URIUtil; import io.usethesource.vallang.ISourceLocation; import io.usethesource.vallang.IValueFactory; import org.rascalmpl.values.ValueFactoryFactory; /** * A generalized Rascal source location breakpoint. * * TODO: create an own RascalLineBreakpoint class inheriting from this class. */ public class RascalSourceLocationBreakpoint extends LineBreakpoint implements IRascalEventListener { /** * Type of the marker. * * @see IMarker#getType() */ protected static final String MARKER_TYPE = "rascal.markerType.sourceLocationBreakpoint"; /* * Custom marker attribute that stores a serialized source location. * * NOTE: #MARKER_ATTRIBUTE_BEGIN_LINE is a synonym to IMarker#LINE_NUMBER * * @see IMarker#setAttribute(String, Object) * @see ISourceLocation */ protected static final String MARKER_ATTRIBUTE_BEGIN_LINE = IMarker.LINE_NUMBER; protected static final String MARKER_ATTRIBUTE_END_LINE = "lineNumberEnd"; protected static final String MARKER_ATTRIBUTE_BEGIN_COLUMN = "beginColumn"; protected static final String MARKER_ATTRIBUTE_END_COLUMN = "endColumn"; protected static final String MARKER_ATTRIBUTE_URI = "uri"; // target currently installed in private RascalDebugTarget fTarget; // resource associated with the marker private IResource resource; /** * Default constructor is required for the breakpoint manager * to re-create persisted breakpoints. After instantiating a breakpoint, * the <code>setMarker(...)</code> method is called to restore * this breakpoint's attributes. */ public RascalSourceLocationBreakpoint() { super(); } /** * Constructs a line breakpoint on the given resource and a complete * source location entry. The line number is 1-based (i.e. the first * line of a file is line number 1, {@link ISourceLocation#getBeginLine()}). * The Rascal VM uses as well source location objects to identify * AST items. * * @param resource file on which to set the breakpoint * @param sourceLocaton fine grained source location of the breakpoint * @throws CoreException if unable to create the breakpoint */ public RascalSourceLocationBreakpoint(final IResource resource, final ISourceLocation sourceLocation) throws CoreException { Assert.isNotNull(resource); Assert.isNotNull(sourceLocation); Assert.isTrue(sourceLocation.hasOffsetLength()); Assert.isTrue(sourceLocation.hasLineColumn()); // initialize attributes this.resource = resource; IWorkspaceRunnable runnable = new IWorkspaceRunnable() { public void run(IProgressMonitor monitor) throws CoreException { IMarker marker = resource.createMarker(MARKER_TYPE); // standard attributes marker.setAttribute(IBreakpoint.ID, getModelIdentifier()); marker.setAttribute(IBreakpoint.ENABLED, Boolean.TRUE); marker.setAttribute(IMarker.MESSAGE, "Line Breakpoint: " + resource.getName() + " [line: " + sourceLocation.getBeginLine() + "]"); // hasOffsetLength() int charStart = sourceLocation.getOffset(); int charEnd = sourceLocation.getLength() + charStart; marker.setAttribute(IMarker.CHAR_START, charStart); marker.setAttribute(IMarker.CHAR_END, charEnd); // custom attributes marker.setAttribute(MARKER_ATTRIBUTE_BEGIN_LINE, sourceLocation.getBeginLine()); marker.setAttribute(MARKER_ATTRIBUTE_END_LINE, sourceLocation.getEndLine()); marker.setAttribute(MARKER_ATTRIBUTE_BEGIN_COLUMN, sourceLocation.getBeginColumn()); marker.setAttribute(MARKER_ATTRIBUTE_END_COLUMN, sourceLocation.getEndColumn()); URI uri = sourceLocation.getURI(); marker.setAttribute(MARKER_ATTRIBUTE_URI, uri.toString()); setMarker(marker); } }; run(getMarkerRule(resource), runnable); } /* (non-Javadoc) * @see org.eclipse.debug.core.model.IBreakpoint#getModelIdentifier() */ public String getModelIdentifier() { return IRascalResources.ID_RASCAL_DEBUG_MODEL; } /* (non-Javadoc) * @see org.eclipse.debug.core.model.Breakpoint#setMarker(org.eclipse.core.resources.IMarker) */ @Override public void setMarker(IMarker marker) throws CoreException { super.setMarker(marker); // restore attributes for persisted breakpoints resource = marker.getResource(); } /** * Returns whether this breakpoint is a run-to-line breakpoint * * @return whether this breakpoint is a run-to-line breakpoint */ public boolean isRunToLineBreakpoint() { return false; } /** * Installs this breakpoint in the given interpreter. * * @param target Rascal interpreter * @throws CoreException if installation fails */ public void install(RascalDebugTarget target) throws CoreException { fTarget = target; target.addEventListener(this); createRequest(target); } /** * Removes this breakpoint from the given interpreter. * Removes this breakpoint as an event listener and clears * the request for the interpreter. * * @param target Rascal interpreter * @throws CoreException if removal fails */ public void remove(RascalDebugTarget target) throws CoreException { clearRequest(target); target.removeEventListener(this); fTarget = null; } /** * Create the breakpoint specific request in the target. Subclasses * should override. * * @param target Rascal interpreter * @throws CoreException if request creation fails */ protected void createRequest(RascalDebugTarget target) throws CoreException { target.sendRequest(requestSetBreakpoint(getSourceLocation())); } /** * Removes this breakpoint's event request from the target. Subclasses * should override. * * @param target Rascal interpreter * @throws CoreException if clearing the request fails */ protected void clearRequest(RascalDebugTarget target) throws CoreException { target.sendRequest(requestDeleteBreakpoint(getSourceLocation())); } /** * Returns the target this breakpoint is installed in or <code>null</code>. * * @return the target this breakpoint is installed in or <code>null</code> */ protected RascalDebugTarget getDebugTarget() { return fTarget; } /** * Returns the resource that is configured or <code>null</code>. * * @return the resource that is configured or <code>null</code>. */ protected IResource getResource() { return resource; } /** * Returns the source location that is configured or <code>null</code>. * The source location is reconstructed from the display {@link IMarker} * and used to detect if a breakpoint was hit. * * @return the source location that is configured or <code>null</code>. */ protected ISourceLocation getSourceLocation() { try { return markerToSourceLocation(getMarker()); } catch (CoreException e) { return null; } } private static ISourceLocation markerToSourceLocation(IMarker marker) throws CoreException { IValueFactory valueFactory = ValueFactoryFactory.getValueFactory(); ISourceLocation result = null; // hasOffsetLength() int offset = (Integer) marker.getAttribute(IMarker.CHAR_START); int length = (Integer) marker.getAttribute(IMarker.CHAR_END) - offset; // hasLineColumn() int beginLine = (Integer) marker.getAttribute(MARKER_ATTRIBUTE_BEGIN_LINE); int endLine = (Integer) marker.getAttribute(MARKER_ATTRIBUTE_END_LINE); int beginCol = (Integer) marker.getAttribute(MARKER_ATTRIBUTE_BEGIN_COLUMN); int endCol = (Integer) marker.getAttribute(MARKER_ATTRIBUTE_END_COLUMN); String uriString = (String) marker.getAttribute(MARKER_ATTRIBUTE_URI); try { result = valueFactory.sourceLocation(valueFactory.sourceLocation(URIUtil.createFromEncoded(uriString)), offset, length, beginLine, endLine, beginCol, endCol); } catch (URISyntaxException e) { IStatus message = new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Persisted URI string of the marker's source location is invalid.", e); throw new CoreException(message); } return result; } /** * Notify's the Rascal interpreter that this breakpoint has been hit. */ private final void notifyThread() { if (fTarget != null) { try { IThread[] threads = fTarget.getThreads(); if (threads.length == 1) { RascalThread thread = (RascalThread)threads[0]; thread.suspendedBy(this); } } catch (DebugException e) { } } } /** * Determines if this breakpoint was hit and notifies the thread. * * @param event breakpoint event */ private void handleHit(RascalEvent event) { ISourceLocation hitLocation = (ISourceLocation) event.getData(); if (hitLocation.equals(getSourceLocation())) { notifyThread(); } } @Override public void handleRascalEvent(RascalEvent event) { if (event.getKind() == RascalEvent.Kind.SUSPEND && event.getDetail() == RascalEvent.Detail.BREAKPOINT) { handleHit(event); } } }