| /********************************************************************** |
| * This file is part of "Object Teams Development Tooling"-Software |
| * |
| * Copyright 2004, 2006 Fraunhofer Gesellschaft, Munich, Germany, |
| * for its Fraunhofer Institute and Computer Architecture and Software |
| * Technology (FIRST), Berlin, Germany and Technical University Berlin, |
| * Germany. |
| * |
| * 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 |
| * $Id: CopyInheritanceBreakpointManager.java 23432 2010-02-03 23:13:42Z stephan $ |
| * |
| * Please visit http://www.objectteams.org for updates and contact. |
| * |
| * Contributors: |
| * Fraunhofer FIRST - Initial API and implementation |
| * Technical University Berlin - Initial API and implementation |
| **********************************************************************/ |
| package org.eclipse.objectteams.otdt.debug.ui.internal; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.core.resources.IMarker; |
| import org.eclipse.core.resources.IMarkerDelta; |
| import org.eclipse.core.resources.IResourceChangeEvent; |
| import org.eclipse.core.resources.IResourceChangeListener; |
| import org.eclipse.core.resources.IResourceDelta; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.SubProgressMonitor; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.debug.core.DebugException; |
| import org.eclipse.debug.core.model.IBreakpoint; |
| import org.eclipse.jdt.core.Flags; |
| import org.eclipse.jdt.core.IType; |
| import org.eclipse.jdt.core.JavaModelException; |
| import org.eclipse.jdt.core.dom.Message; |
| import org.eclipse.jdt.debug.core.IJavaBreakpoint; |
| import org.eclipse.jdt.debug.core.IJavaBreakpointListener; |
| import org.eclipse.jdt.debug.core.IJavaDebugTarget; |
| import org.eclipse.jdt.debug.core.IJavaInterfaceType; |
| import org.eclipse.jdt.debug.core.IJavaLineBreakpoint; |
| import org.eclipse.jdt.debug.core.IJavaThread; |
| import org.eclipse.jdt.debug.core.IJavaType; |
| import org.eclipse.jdt.debug.core.JDIDebugModel; |
| import org.eclipse.jdt.internal.debug.ui.BreakpointUtils; |
| import org.eclipse.jface.operation.IRunnableWithProgress; |
| import org.eclipse.objectteams.otdt.core.IOTType; |
| import org.eclipse.objectteams.otdt.core.IOTTypeHierarchy; |
| import org.eclipse.objectteams.otdt.core.IRoleType; |
| import org.eclipse.objectteams.otdt.core.OTModelManager; |
| import org.eclipse.objectteams.otdt.core.compiler.ISMAPConstants; |
| import org.eclipse.objectteams.otdt.debug.ui.OTDebugUIPlugin; |
| |
| /** |
| * This class deals with the problem that a breakpoint in role-method won't have an effect |
| * in tsub-classes. This is due to the fact, that methods are inherited through CopyInheritance, |
| * that is, their byte-code is copied to the tsub-class. |
| * |
| * This class sort of performs copy-inheritance for breakpoints. I.e. whenever a breakpoint |
| * is installed for a role-method, it will be installed in the copied methods of all tsub-classes |
| * as well. |
| * |
| * When uninstalling a breakpoint for a target, the copies are removed from that target, |
| * and completely removed if no longer installed in any targets. |
| * |
| * Mapping of line numbers is handled by a JavaStratumLineBreakpoint for stratum "OTJ". |
| * |
| * @author gis, stephan |
| */ |
| public class CopyInheritanceBreakpointManager implements IJavaBreakpointListener, IResourceChangeListener |
| { |
| private static final Object OT_BREAKPOINT_COPY = "OT_BREAKPOINT_COPY"; //$NON-NLS-1$ |
| |
| private static final String ROLE_CLASS_SEPARATOR = "$__OT__"; //$NON-NLS-1$ |
| |
| /** keep our own mapping from original breakpoints to copies (the breakpoint manager only knows installed breakpoints). */ |
| private Map<IMarker, List<IJavaBreakpoint>> copiedBreakpoints = new HashMap<IMarker, List<IJavaBreakpoint>>(); |
| |
| public CopyInheritanceBreakpointManager() |
| { |
| super(); |
| } |
| |
| public void addingBreakpoint(IJavaDebugTarget target, IJavaBreakpoint breakpoint) {} |
| public void breakpointInstalled(IJavaDebugTarget target, IJavaBreakpoint breakpoint) {} |
| public void breakpointHasRuntimeException(IJavaLineBreakpoint breakpoint, DebugException exception) {} |
| public void breakpointHasCompilationErrors(IJavaLineBreakpoint breakpoint, Message[] errors) {} |
| public int breakpointHit(IJavaThread thread, IJavaBreakpoint breakpoint) |
| { |
| return IJavaBreakpointListener.DONT_CARE; |
| } |
| |
| /** |
| * The debugger signals that a breakpoint is being installed into the VM. |
| * Check if we need to add copies into tsub roles. |
| */ |
| public int installingBreakpoint(IJavaDebugTarget target, IJavaBreakpoint breakpoint, IJavaType type) |
| { |
| try { |
| if (isBreakpointCopy(breakpoint)) |
| return IJavaBreakpointListener.INSTALL; // yes please! (no further copying needed) |
| |
| if (!(type instanceof IJavaInterfaceType)) |
| return IJavaBreakpointListener.DONT_CARE; // only use ifc part, (tsuper-) class may not be loaded |
| |
| if (breakpoint instanceof IJavaLineBreakpoint) |
| addTSubBreakpointsFor(type, (IJavaLineBreakpoint) breakpoint, target); |
| } |
| catch (CoreException ex) |
| { |
| OTDebugUIPlugin.getExceptionHandler().logCoreException("Problem with breakpoint handling", ex); //$NON-NLS-1$ |
| } |
| |
| return IJavaBreakpointListener.DONT_CARE; |
| } |
| |
| private boolean isBreakpointCopy(IJavaBreakpoint breakpoint) throws CoreException |
| { |
| IMarker marker = breakpoint.getMarker(); |
| if (marker == null) |
| return false; |
| |
| Map properties = marker.getAttributes(); |
| return properties.containsKey(OT_BREAKPOINT_COPY); |
| } |
| |
| private void addTSubBreakpointsFor(IJavaType triggerType, IJavaLineBreakpoint breakpoint, IJavaDebugTarget target) |
| throws CoreException |
| { |
| IMarker marker = breakpoint.getMarker(); |
| IType type = BreakpointUtils.getType(breakpoint); |
| |
| // only act when triggered by the class of the breakpoint (which must be a role): |
| IOTType otType = OTModelManager.getOTElement(type); |
| if (otType == null) |
| return; |
| if (!otType.getFullyQualifiedName('$').equals(triggerType.getName())) |
| return; |
| |
| // check whether copies have already been created: |
| List<IJavaBreakpoint> existingCopies = this.copiedBreakpoints.get(marker); |
| if (existingCopies != null) { |
| // tsub breakpoints are already created, only add them to this target: |
| for (IJavaBreakpoint existingCopy : existingCopies) |
| target.breakpointAdded(existingCopy); |
| return; |
| } |
| |
| // find tsub roles to install into: |
| IType[] tsubClasses = new TSubClassComputer((IRoleType) otType).getSubClasses(); |
| if (tsubClasses == null || tsubClasses.length == 0) |
| return; |
| |
| // perform: |
| String fileName = type.getCompilationUnit().getElementName(); |
| List<IJavaBreakpoint> newBreakpoints = new ArrayList<IJavaBreakpoint>(tsubClasses.length); |
| for (IType tsubClass : tsubClasses) { |
| IJavaLineBreakpoint newBreakpoint = propagateBreakpoint(breakpoint, fileName, tsubClass, target); |
| if (newBreakpoint != null) |
| newBreakpoints.add(newBreakpoint); |
| } |
| |
| this.copiedBreakpoints.put(marker, newBreakpoints); |
| |
| } |
| |
| /** |
| * Propagate the given breakpoint to one tsub role. |
| * |
| * @param breakpoint breakpoint to copy |
| * @param fileName name of the source file that implements the given line |
| * @param destType the tsub role into which to install |
| * @param target the debug target into which the breakpoint should be installed |
| * @return a new breakpoint or null |
| * @throws CoreException when accessing the existing breakpoint fails or the marker for the new breakpoint could not be created |
| */ |
| private IJavaLineBreakpoint propagateBreakpoint(IJavaLineBreakpoint breakpoint, String fileName, IType destType, IJavaDebugTarget target) |
| throws CoreException |
| { |
| if (destType == null) |
| { |
| OTDebugUIPlugin.getDefault().getLog().log(new Status(IStatus.ERROR, |
| OTDebugUIPlugin.PLUGIN_ID, |
| "CopyInheritanceBreakpointManager.propagateBreakpoint(): tsub type is null")); //$NON-NLS-1$ |
| return null; |
| } |
| |
| Exception ex = null; |
| int sourceLineNumber = -1; |
| try { |
| sourceLineNumber = breakpoint.getLineNumber(); |
| } catch (CoreException ce) { |
| ex = ce; |
| } |
| if (sourceLineNumber == -1 || ex != null) |
| { |
| OTDebugUIPlugin.getDefault().getLog().log(new Status(IStatus.ERROR, |
| OTDebugUIPlugin.PLUGIN_ID, |
| "CopyInheritanceBreakpointManager.propagateBreakpoint(): source line number not found", //$NON-NLS-1$ |
| ex)); |
| return null; |
| } |
| |
| return duplicateBreakpoint(breakpoint, fileName, destType, sourceLineNumber, target); |
| } |
| |
| /** |
| * Duplicate the given breakpoint for the given tsub-role. |
| * |
| * @param breakpoint breakpoint to copy |
| * @param fileName name of the source file that implements the given line |
| * @param destType the tsub role into which to install |
| * @param sourceLineNumber line number within fileName |
| * @param target the debug target into which the breakpoint should be installed |
| * @return a new breakpoint, never null; |
| * @throws CoreException when accessing the existing breakpoint fails or the marker for the new breakpoint could not be created |
| */ |
| private IJavaLineBreakpoint duplicateBreakpoint(IJavaLineBreakpoint breakpoint, String fileName, IType destType, int sourceLineNumber, IJavaDebugTarget target) |
| throws CoreException |
| { |
| // FIXME: other breakpoint types, exception, properties (null) |
| Map properties = getBreakpointProperties(breakpoint); |
| Boolean origEnabled = (Boolean) properties.get(IBreakpoint.ENABLED); |
| String destName = getClassPartName(destType); |
| // Note: by marking the breakpoint as unregistered, we prevent it from showing up in the breakpoints view. |
| // Conversely this means that we can not rely on the breakpoint manager but must maintain our own registry (copiedBreakpoints) |
| IJavaLineBreakpoint newBreakpoint = JDIDebugModel.createStratumBreakpoint( |
| breakpoint.getMarker().getResource(), |
| ISMAPConstants.OTJ_STRATUM_NAME, |
| fileName, |
| null, //sourcePath, |
| destName, // classNamePattern |
| sourceLineNumber, |
| -1, -1, // charStart, charEnd |
| breakpoint.getHitCount(), |
| false, |
| properties); |
| // restore one attribute that is hardcoded in JavaStratumLineBreakpoint.<init>: |
| if (!origEnabled) |
| try { |
| newBreakpoint.getMarker().setAttribute(IBreakpoint.ENABLED, Boolean.FALSE); |
| } catch (CoreException ex) { |
| OTDebugUIPlugin.getExceptionHandler().logCoreException("Unable to disable breakpoint", ex); //$NON-NLS-1$ |
| } |
| target.getDebugTarget().breakpointAdded(newBreakpoint); |
| |
| return newBreakpoint; |
| } |
| |
| /** |
| * If type is a role return the name of its class-part, |
| * and ensure all enclosing role-teams are given by their class-part, too. |
| */ |
| String getClassPartName(IType type) { |
| IType enclosing = type.getDeclaringType(); |
| try { |
| if ( enclosing != null |
| && Flags.isTeam(enclosing.getFlags())) |
| return getClassPartName(enclosing)+ROLE_CLASS_SEPARATOR+type.getElementName(); |
| } catch (JavaModelException e) { |
| // fall through |
| } |
| return type.getFullyQualifiedName(); |
| } |
| |
| /** Initialize breakpoint properties from `breakpoint' and add a few values specific to copies. */ |
| private Map getBreakpointProperties(IJavaLineBreakpoint breakpoint) |
| { |
| Map properties = new HashMap(13); |
| try { |
| properties.putAll(breakpoint.getMarker().getAttributes()); |
| } catch (CoreException e) { |
| // couldn't read marker attributes |
| } |
| properties.put(IBreakpoint.PERSISTED, Boolean.FALSE); |
| properties.put(IMarker.SEVERITY, new Integer(IMarker.SEVERITY_WARNING)); // hide from the ruler |
| properties.put(OT_BREAKPOINT_COPY, Boolean.TRUE); |
| |
| return properties; |
| } |
| |
| /** |
| * A breakpoint has been removed from a target. Do the same for copies of this breakpoint. |
| * |
| * @param target the debug target from which the breakpoint has been removed |
| * @param breakpoint the breakpoint (potential tsuper). |
| */ |
| public void breakpointRemoved(IJavaDebugTarget target, IJavaBreakpoint breakpoint) |
| { |
| IMarker marker = breakpoint.getMarker(); |
| if (marker == null || !marker.exists()) |
| return; |
| |
| // retrieve breakpoint and disable it: |
| List<IJavaBreakpoint> copies = this.copiedBreakpoints.get(marker); |
| if (copies == null) |
| return; |
| |
| List<IJavaBreakpoint> remainingCopies = new ArrayList<IJavaBreakpoint>(copies.size()); |
| for (IJavaBreakpoint copy : copies) { |
| target.getDebugTarget().breakpointRemoved(copy, null); |
| try { |
| if (copy.isInstalled()) |
| remainingCopies.add(copy); |
| else |
| copy.delete(); |
| } catch (CoreException ex) { |
| OTDebugUIPlugin.getExceptionHandler().logCoreException("Unable to query copied breakpoint", ex); //$NON-NLS-1$ |
| } |
| } |
| // cleanup if no more copies are active: |
| if (remainingCopies.size() == 0) |
| this.copiedBreakpoints.remove(marker); |
| // cleanup if fewer copies are active: |
| else if (remainingCopies.size() != copies.size()) |
| this.copiedBreakpoints.put(marker, remainingCopies); |
| } |
| |
| private class TSubClassComputer implements IRunnableWithProgress |
| { |
| private IOTTypeHierarchy _hierarchy; |
| private IRoleType _roleType; |
| private IType[] _subClasses; |
| |
| public TSubClassComputer(IRoleType otType) |
| { |
| _roleType = otType; |
| } |
| |
| public void run(IProgressMonitor monitor) |
| throws InvocationTargetException, InterruptedException |
| { |
| monitor.beginTask(BreakpointMessages.CopyInheritanceBreakpointManager_find_tsub_types_task, 1); |
| IProgressMonitor mon = new SubProgressMonitor(monitor, 1); |
| try |
| { |
| _hierarchy = _roleType.newOTTypeHierarchy(mon); |
| _hierarchy.setPhantomMode(true); |
| _subClasses = _hierarchy.getAllTSubtypes((IType)_roleType); |
| |
| } |
| catch (JavaModelException ex) |
| { |
| throw new InvocationTargetException(ex); |
| } |
| finally |
| { |
| mon.done(); |
| } |
| } |
| |
| /** |
| * @return null or the computed tsub classes |
| */ |
| public IType[] getSubClasses() |
| { |
| try |
| { |
| // PlatformUI.getWorkbench().getProgressService().busyCursorWhile(this); |
| run(new NullProgressMonitor()); |
| } |
| catch (InvocationTargetException ex) |
| { |
| if (ex.getCause() instanceof CoreException) |
| OTDebugUIPlugin.getExceptionHandler().logCoreException("Error creating type hiearchy", (CoreException) ex.getCause()); //$NON-NLS-1$ |
| else |
| OTDebugUIPlugin.getExceptionHandler().logException("Error creating type hiearchy", ex); //$NON-NLS-1$ |
| } |
| catch (InterruptedException ex) |
| { |
| OTDebugUIPlugin.getExceptionHandler().logException("Error creating type hiearchy", ex); //$NON-NLS-1$ |
| } |
| |
| return _subClasses; |
| } |
| } |
| |
| /** |
| * Watch for changes of the ENABLED attribute of breakpoint markers. |
| * |
| * @param event the change event. |
| */ |
| public void resourceChanged(IResourceChangeEvent event) |
| { |
| IMarkerDelta[] markerDeltas = event.findMarkerDeltas(IBreakpoint.BREAKPOINT_MARKER, true); |
| if (markerDeltas == null) |
| return; |
| for (IMarkerDelta markerDelta : markerDeltas) { |
| if (markerDelta.getKind() == IResourceDelta.CHANGED) { |
| IMarker marker = markerDelta.getMarker(); |
| final Boolean oldEnabled = markerDelta.getAttribute(IBreakpoint.ENABLED, Boolean.FALSE); |
| final Boolean newEnabled = marker.getAttribute(IBreakpoint.ENABLED, Boolean.FALSE); |
| if (!oldEnabled.equals(newEnabled)) { |
| // we have a change wrt enablement |
| List<IJavaBreakpoint> breakpoints = this.copiedBreakpoints.get(marker); |
| if (breakpoints != null) { |
| for (final IJavaBreakpoint copy : breakpoints) { |
| Job job = new Job(BreakpointMessages.CopyInheritanceBreakpointManager_toggle_enablement_job) { |
| protected IStatus run(IProgressMonitor monitor) { |
| try { |
| copy.setEnabled(newEnabled); |
| return Status.OK_STATUS; |
| } catch (CoreException e) { |
| return new Status(IStatus.ERROR, OTDebugUIPlugin.PLUGIN_ID, "Error toggling copied breakpoint enablement", e); //$NON-NLS-1$ |
| } |
| } |
| }; |
| job.setRule(event.getResource()); |
| job.schedule(); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |