| /******************************************************************************* |
| * Copyright (c) 2000, 2016 IBM Corporation 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: |
| * IBM Corporation - initial API and implementation |
| * Yevgen Kogan - Bug 403475 - Hot Code Replace drops too much frames in some cases |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.debug.core.hcr; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IMarker; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IResourceChangeEvent; |
| import org.eclipse.core.resources.IResourceChangeListener; |
| import org.eclipse.core.resources.IResourceDelta; |
| import org.eclipse.core.resources.IResourceDeltaVisitor; |
| import org.eclipse.core.resources.IWorkspace; |
| import org.eclipse.core.resources.IncrementalProjectBuilder; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IAdaptable; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.ListenerList; |
| import org.eclipse.core.runtime.MultiStatus; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.debug.core.DebugEvent; |
| import org.eclipse.debug.core.DebugException; |
| import org.eclipse.debug.core.DebugPlugin; |
| import org.eclipse.debug.core.IDebugEventSetListener; |
| import org.eclipse.debug.core.ILaunch; |
| import org.eclipse.debug.core.ILaunchListener; |
| import org.eclipse.debug.core.ILaunchManager; |
| import org.eclipse.debug.core.model.IDebugTarget; |
| import org.eclipse.debug.core.model.IThread; |
| import org.eclipse.jdt.core.ICompilationUnit; |
| import org.eclipse.jdt.core.IJavaElement; |
| import org.eclipse.jdt.core.IJavaModelMarker; |
| import org.eclipse.jdt.core.IJavaProject; |
| import org.eclipse.jdt.core.IMethod; |
| import org.eclipse.jdt.core.IType; |
| import org.eclipse.jdt.core.JavaCore; |
| import org.eclipse.jdt.core.JavaModelException; |
| import org.eclipse.jdt.core.Signature; |
| import org.eclipse.jdt.core.ToolFactory; |
| import org.eclipse.jdt.core.util.IClassFileReader; |
| import org.eclipse.jdt.core.util.ISourceAttribute; |
| import org.eclipse.jdt.debug.core.IJavaDebugTarget; |
| import org.eclipse.jdt.debug.core.IJavaHotCodeReplaceListener; |
| import org.eclipse.jdt.debug.core.IJavaStackFrame; |
| import org.eclipse.jdt.debug.core.IJavaThread; |
| import org.eclipse.jdt.debug.core.JDIDebugModel; |
| import org.eclipse.jdt.internal.core.util.Util; |
| import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin; |
| import org.eclipse.jdt.internal.debug.core.JavaDebugUtils; |
| import org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget; |
| import org.eclipse.jdt.internal.debug.core.model.JDIStackFrame; |
| import org.eclipse.jdt.internal.debug.core.model.JDIThread; |
| |
| import com.ibm.icu.text.MessageFormat; |
| import com.sun.jdi.IncompatibleThreadStateException; |
| import com.sun.jdi.ReferenceType; |
| import com.sun.jdi.VirtualMachine; |
| |
| /** |
| * The hot code replace manager listens for changes to class files and notifies |
| * running debug targets of the changes. |
| * <p> |
| * Currently, replacing .jar files has no effect on running targets. |
| */ |
| public class JavaHotCodeReplaceManager implements IResourceChangeListener, |
| ILaunchListener, IDebugEventSetListener { |
| /** |
| * Singleton |
| */ |
| private static JavaHotCodeReplaceManager fgInstance = null; |
| /** |
| * The class file extension |
| */ |
| private static final String CLASS_FILE_EXTENSION = "class"; //$NON-NLS-1$ |
| |
| /** |
| * The list of <code>IJavaHotCodeReplaceListeners</code> which this hot code |
| * replace manager will notify about hot code replace attempts. |
| */ |
| private ListenerList<IJavaHotCodeReplaceListener> fHotCodeReplaceListeners = new ListenerList<>(); |
| |
| /** |
| * The lists of hot swap targets which support HCR and those which don't |
| */ |
| private ArrayList<JDIDebugTarget> fHotSwapTargets = new ArrayList<>(1); |
| private ArrayList<JDIDebugTarget> fNoHotSwapTargets = new ArrayList<>(1); |
| |
| /** |
| * A mapping of the last time projects were built. |
| * <ol> |
| * <li>key: project (IProject)</li> |
| * <li>value: build date (ProjectBuildTime)</li> |
| * </ol> |
| */ |
| private Map<IProject, ProjectBuildTime> fProjectBuildTimes = new HashMap<>(); |
| private static Date fStartupDate = new Date(); |
| |
| /** |
| * Cache of compilation unit deltas renewed on each HCR attempt. |
| */ |
| private Map<ICompilationUnit, CompilationUnitDelta> fDeltaCache = new HashMap<>(); |
| |
| /** |
| * Utility object used for tracking build times of projects. The HCR manager |
| * receives notification of builds AFTER the build has occurred but BEFORE |
| * the classfile resource changed deltas are fired. Thus, when the current |
| * build time is set, we need to hang onto the last build time so that we |
| * can use the last build time for comparing changes to compilation units |
| * (for smart drop to frame). |
| */ |
| class ProjectBuildTime { |
| private Date fCurrentDate = new Date(); |
| private Date fPreviousDate = new Date(); |
| |
| public void setCurrentBuildDate(Date date) { |
| fPreviousDate = fCurrentDate; |
| fCurrentDate = date; |
| } |
| |
| public void setLastBuildDate(Date date) { |
| fPreviousDate = date; |
| if (fPreviousDate.getTime() > fCurrentDate.getTime()) { |
| // If the previous date is set later than the current |
| // date, move the current date up to the previous. |
| fCurrentDate = fPreviousDate; |
| } |
| } |
| |
| /** |
| * Returns the last build time |
| */ |
| public Date getLastBuildDate() { |
| return fPreviousDate; |
| } |
| } |
| |
| /** |
| * Visitor for resource deltas. |
| */ |
| protected ChangedClassFilesVisitor fClassfileVisitor = new ChangedClassFilesVisitor(); |
| |
| /** |
| * Creates a new HCR manager |
| */ |
| private JavaHotCodeReplaceManager() { |
| } |
| |
| /** |
| * Returns the singleton HCR manager |
| */ |
| public static synchronized JavaHotCodeReplaceManager getDefault() { |
| if (fgInstance == null) { |
| fgInstance = new JavaHotCodeReplaceManager(); |
| } |
| return fgInstance; |
| } |
| |
| /** |
| * Registers this HCR manager as a resource change listener. This method is |
| * called by the JDI debug model plug-in on startup. |
| */ |
| public void startup() { |
| DebugPlugin.getDefault().getLaunchManager().addLaunchListener(this); |
| DebugPlugin.getDefault().addDebugEventListener(this); |
| } |
| |
| /** |
| * unregisters this HCR manager as a resource change listener. Removes all |
| * hot code replace listeners. This method is called by the JDI debug model |
| * plug-in on shutdown. |
| */ |
| public void shutdown() { |
| DebugPlugin.getDefault().getLaunchManager().removeLaunchListener(this); |
| DebugPlugin.getDefault().removeDebugEventListener(this); |
| getWorkspace().removeResourceChangeListener(this); |
| fHotCodeReplaceListeners = new ListenerList<>(); |
| synchronized (this) { |
| fHotSwapTargets.clear(); |
| fNoHotSwapTargets.clear(); |
| } |
| } |
| |
| /** |
| * Returns the workspace. |
| */ |
| protected IWorkspace getWorkspace() { |
| return ResourcesPlugin.getWorkspace(); |
| } |
| |
| /** |
| * Returns the launch manager. |
| */ |
| protected ILaunchManager getLaunchManager() { |
| return DebugPlugin.getDefault().getLaunchManager(); |
| } |
| |
| /** |
| * @see IResourceChangeListener#resourceChanged(org.eclipse.core.resources.IResourceChangeEvent) |
| */ |
| @Override |
| public void resourceChanged(IResourceChangeEvent event) { |
| List<IProject> projects = getBuiltProjects(event); |
| if (!projects.isEmpty()) { |
| updateProjectBuildTime(projects); |
| } |
| synchronized (this) { |
| if (fHotSwapTargets.isEmpty() && fNoHotSwapTargets.isEmpty()) { |
| // If there are no targets to notify, only update the build |
| // times. |
| return; |
| } |
| } |
| ChangedClassFilesVisitor visitor = getChangedClassFiles(event); |
| if (visitor != null) { |
| List<IResource> resources = visitor.getChangedClassFiles(); |
| List<String> names = visitor.getQualifiedNamesList(); |
| if (!resources.isEmpty()) { |
| notifyTargets(resources, names); |
| } |
| } |
| } |
| |
| /** |
| * Returns all projects which this event says may have been built. |
| */ |
| protected List<IProject> getBuiltProjects(IResourceChangeEvent event) { |
| IResourceDelta delta = event.getDelta(); |
| if (event.getType() != IResourceChangeEvent.POST_BUILD || delta == null |
| || event.getBuildKind() == 0) { |
| return Collections.EMPTY_LIST; |
| } |
| if (event.getBuildKind() == IncrementalProjectBuilder.AUTO_BUILD |
| && !ResourcesPlugin.getWorkspace().isAutoBuilding()) { |
| // If this is an auto build and the workspace is not autobuilding, |
| // no projects will actually be compiled. |
| return Collections.EMPTY_LIST; |
| } |
| Object source = event.getSource(); |
| if (source instanceof IProject) { |
| List<IProject> list = new ArrayList<>(); |
| list.add((IProject) source); |
| return list; |
| } else if (source instanceof IWorkspace) { |
| IProject[] allProjects = ((IWorkspace) source).getRoot() |
| .getProjects(); |
| return Arrays.asList(allProjects); |
| } |
| return Collections.EMPTY_LIST; |
| } |
| |
| /** |
| * If the given event contains a build notification, update the last build |
| * time of the corresponding project |
| */ |
| private void updateProjectBuildTime(List<IProject> projects) { |
| Date currentDate = new Date(); |
| ProjectBuildTime buildTime = null; |
| for(IProject project : projects) { |
| buildTime = fProjectBuildTimes.get(project); |
| if (buildTime == null) { |
| buildTime = new ProjectBuildTime(); |
| fProjectBuildTimes.put(project, buildTime); |
| } |
| buildTime.setCurrentBuildDate(currentDate); |
| } |
| } |
| |
| /** |
| * Returns the last known build time for the given project. If no build time |
| * is known for the given project, the last known build time for the project |
| * is set to the hot code replace manager's startup time. |
| */ |
| protected long getLastProjectBuildTime(IProject project) { |
| ProjectBuildTime time = fProjectBuildTimes |
| .get(project); |
| if (time == null) { |
| time = new ProjectBuildTime(); |
| time.setLastBuildDate(fStartupDate); |
| fProjectBuildTimes.put(project, time); |
| } |
| return time.getLastBuildDate().getTime(); |
| } |
| |
| /** |
| * Notifies the targets of the changed types |
| */ |
| private void notifyTargets(final List<IResource> resources, final List<String> qualifiedNames) { |
| final List<JDIDebugTarget> hotSwapTargets = getHotSwapTargets(); |
| final List<JDIDebugTarget> noHotSwapTargets = getNoHotSwapTargets(); |
| if (!hotSwapTargets.isEmpty()) { |
| Runnable runnable = new Runnable() { |
| @Override |
| public void run() { |
| doHotCodeReplace(hotSwapTargets, resources, qualifiedNames); |
| } |
| }; |
| DebugPlugin.getDefault().asyncExec(runnable); |
| } |
| if (!noHotSwapTargets.isEmpty()) { |
| Runnable runnable = new Runnable() { |
| @Override |
| public void run() { |
| notifyUnsupportedHCR(noHotSwapTargets, resources, |
| qualifiedNames); |
| } |
| }; |
| DebugPlugin.getDefault().asyncExec(runnable); |
| } |
| } |
| |
| /** |
| * Filters elements out of the given collections of resources and qualified |
| * names if there is no type corresponding type loaded in the given debug |
| * target. This method allows us to avoid bogus HCR attempts and |
| * "HCR failed" notifications. |
| * |
| * @param target |
| * the debug target |
| * @param resources |
| * the list of resources to filter |
| * @param qualifiedNames |
| * the list of qualified names to filter, which corresponds to |
| * the list of resources on a one-to-one-basis |
| */ |
| private void filterUnloadedTypes(JDIDebugTarget target, List<IResource> resources, |
| List<String> qualifiedNames) { |
| for (int i = 0, numElements = qualifiedNames.size(); i < numElements; i++) { |
| String name = qualifiedNames.get(i); |
| List<ReferenceType> list = target.jdiClassesByName(name); |
| if (list.isEmpty()) { |
| // If no classes with the given name are loaded in the VM, don't |
| // waste |
| // cycles trying to replace. |
| qualifiedNames.remove(i); |
| resources.remove(i); |
| // Decrement the index and number of elements to compensate for |
| // item removal |
| i--; |
| numElements--; |
| } |
| } |
| } |
| |
| /** |
| * Notify the given targets that HCR failed for classes with the given fully |
| * qualified names. |
| */ |
| protected void notifyUnsupportedHCR(List<JDIDebugTarget> targets, List<IResource> resources, |
| List<String> qualifiedNames) { |
| Iterator<JDIDebugTarget> iter = targets.iterator(); |
| JDIDebugTarget target = null; |
| while (iter.hasNext()) { |
| target = iter.next(); |
| if (target.isAvailable()) { |
| // Make a local copy of the resources/names to swap so we can |
| // filter |
| // unloaded types on a per-target basis. |
| List<IResource> resourcesToReplace = new ArrayList<>(resources); |
| List<String> qualifiedNamesToReplace = new ArrayList<>(qualifiedNames); |
| filterUnloadedTypes(target, resourcesToReplace, |
| qualifiedNamesToReplace); |
| |
| if (!qualifiedNamesToReplace.isEmpty()) { |
| // Don't notify if the changed types aren't loaded. |
| fireHCRFailed(target, null); |
| notifyFailedHCR(target, qualifiedNamesToReplace); |
| } |
| } else { |
| // Targets should be unregistered when they terminate, |
| // but this is a fall-back. |
| deregisterTarget(target); |
| } |
| } |
| } |
| |
| protected void notifyFailedHCR(JDIDebugTarget target, List<String> qualifiedNames) { |
| if (target.isAvailable()) { |
| target.addOutOfSynchTypes(qualifiedNames); |
| target.fireChangeEvent(DebugEvent.STATE); |
| } |
| } |
| |
| /** |
| * Returns the currently registered debug targets that support hot code |
| * replace. |
| */ |
| protected synchronized List<JDIDebugTarget> getHotSwapTargets() { |
| return new ArrayList<>(fHotSwapTargets); |
| } |
| |
| /** |
| * Returns the currently registered debug targets that do not support hot |
| * code replace. |
| */ |
| protected synchronized List<JDIDebugTarget> getNoHotSwapTargets() { |
| return new ArrayList<>(fNoHotSwapTargets); |
| } |
| |
| /** |
| * Perform a hot code replace with the given resources. For a JDK 1.4 |
| * compliant VM this involves: |
| * <ol> |
| * <li>Popping all frames from all thread stacks which will be affected by |
| * reloading the given resources</li> |
| * <li>Telling the VirtualMachine to redefine the affected classes</li> |
| * <li>Performing a step-into operation on all threads which were affected |
| * by the class redefinition. This returns execution to the first (deepest) |
| * affected method on the stack</li> |
| * </ol> |
| * For a J9 compliant VM this involves: |
| * <ol> |
| * <li>Telling the VirtualMachine to redefine the affected classes</li> |
| * <li>Popping all frames from all thread stacks which were affected by |
| * reloading the given resources and then performing a step-into operation |
| * on all threads which were affected by the class redefinition.</li> |
| * </ol> |
| * |
| * @param targets |
| * the targets in which to perform HCR |
| * @param resources |
| * the resources which correspond to the changed classes |
| */ |
| private void doHotCodeReplace(List<JDIDebugTarget> targets, List<IResource> resources, |
| List<String> qualifiedNames) { |
| |
| // Check whether hot code replace is enabled |
| if (!Platform.getPreferencesService().getBoolean( |
| JDIDebugPlugin.getUniqueIdentifier(), |
| JDIDebugPlugin.PREF_ENABLE_HCR, |
| true, |
| null)) { |
| return; // disabled |
| } |
| |
| MultiStatus ms = new MultiStatus( |
| JDIDebugPlugin.getUniqueIdentifier(), |
| DebugException.TARGET_REQUEST_FAILED, |
| "At least one target failed to drop to frame after successful hot code replace.", null); //$NON-NLS-1$ |
| Iterator<JDIDebugTarget> iter = targets.iterator(); |
| while (iter.hasNext()) { |
| JDIDebugTarget target = iter.next(); |
| if (!target.isAvailable()) { |
| deregisterTarget(target); |
| continue; |
| } |
| // Make a local copy of the resources/names to swap so we can filter |
| // unloaded types on a per-target basis. |
| List<IResource> resourcesToReplace = new ArrayList<>(resources); |
| List<String> qualifiedNamesToReplace = new ArrayList<>(qualifiedNames); |
| |
| // Make sure we only try to replace types from related projects |
| filterUnrelatedResources(target, resourcesToReplace, qualifiedNamesToReplace); |
| if (qualifiedNamesToReplace.isEmpty()) { |
| // If none of the changed types are related to our target, do nothing. |
| continue; |
| } |
| |
| filterUnloadedTypes(target, resourcesToReplace, qualifiedNamesToReplace); |
| if (qualifiedNamesToReplace.isEmpty()) { |
| // If none of the changed types are loaded, do nothing. |
| continue; |
| } |
| |
| List<IThread> poppedThreads = new ArrayList<>(); |
| target.setIsPerformingHotCodeReplace(true); |
| try { |
| boolean framesPopped = false; |
| if (target.canPopFrames()) { |
| // JDK 1.4 drop to frame support: |
| // JDK 1.4 spec is faulty around methods that have |
| // been rendered obsolete after class redefinition. |
| // Thus, pop the frames that contain affected methods |
| // *before* the class redefinition to avoid problems. |
| try { |
| attemptPopFrames(target, resourcesToReplace, |
| qualifiedNamesToReplace, poppedThreads); |
| framesPopped = true; // No exception occurred |
| } catch (DebugException de) { |
| if (shouldLogHCRException(de)) { |
| ms.merge(de.getStatus()); |
| } |
| } |
| } |
| target.removeOutOfSynchTypes(qualifiedNamesToReplace); |
| if (target.supportsJDKHotCodeReplace()) { |
| redefineTypesJDK(target, resourcesToReplace, |
| qualifiedNamesToReplace); |
| } else if (target.supportsJ9HotCodeReplace()) { |
| redefineTypesJ9(target, qualifiedNamesToReplace); |
| } |
| if (containsObsoleteMethods(target)) { |
| fireObsoleteMethods(target); |
| } |
| try { |
| if (target.canPopFrames() && framesPopped) { |
| // Second half of JDK 1.4 drop to frame support: |
| // All affected frames have been popped and the classes |
| // have been reloaded. Step into the first changed |
| // frame of each affected thread. |
| // must re-set 'is doing HCR' to be able to step |
| target.setIsPerformingHotCodeReplace(false); |
| attemptStepIn(poppedThreads); |
| } else { |
| // J9 drop to frame support: |
| // After redefining classes, drop to frame |
| attemptDropToFrame(target, resourcesToReplace, |
| qualifiedNamesToReplace); |
| } |
| } catch (DebugException de) { |
| if (shouldLogHCRException(de)) { |
| ms.merge(de.getStatus()); |
| } |
| } |
| fireHCRSucceeded(target); |
| } catch (DebugException de) { |
| // target update failed |
| fireHCRFailed(target, de); |
| } |
| // also re-set 'is doing HCR' here in case HCR failed |
| target.setIsPerformingHotCodeReplace(false); |
| target.fireChangeEvent(DebugEvent.CONTENT); |
| } |
| if (!ms.isOK()) { |
| JDIDebugPlugin.log(ms); |
| } |
| fDeltaCache.clear(); |
| } |
| |
| private void filterUnrelatedResources(JDIDebugTarget target, List<IResource> resourcesToReplace, List<String> qualifiedNamesToReplace) { |
| Iterator<IResource> resources = resourcesToReplace.iterator(); |
| Iterator<String> names = qualifiedNamesToReplace.iterator(); |
| while (resources.hasNext()) { |
| boolean supported = target.supportsResource(() -> names.next(), resources.next()); |
| if (!supported) { |
| resources.remove(); |
| names.remove(); |
| } |
| } |
| } |
| |
| /** |
| * Returns whether the given exception, which occurred during HCR, should be |
| * logged. We anticipate that we can get IncompatibleThreadStateExceptions |
| * if the user happens to resume a thread at just the right moment. Since |
| * this has no ill effects for HCR, we don't log these exceptions. |
| */ |
| private boolean shouldLogHCRException(DebugException exception) { |
| return !(exception.getStatus().getException() instanceof IncompatibleThreadStateException |
| || exception.getStatus().getCode() == IJavaThread.ERR_INCOMPATIBLE_THREAD_STATE || exception |
| .getStatus().getCode() == IJavaThread.ERR_THREAD_NOT_SUSPENDED); |
| } |
| |
| /** |
| * Replaces the given types in the given J9 debug target. A fully qualified |
| * name of each type must be supplied. |
| * |
| * Breakpoints are reinstalled automatically when the new types are loaded. |
| * |
| * @exception DebugException |
| * if this method fails. Reasons include: |
| * <ul> |
| * <li>Failure communicating with the VM. The |
| * DebugException's status code contains the underlying |
| * exception responsible for the failure.</li> |
| * <li>The target VM was unable to reload a type due to a |
| * shape change</li> |
| * </ul> |
| */ |
| private void redefineTypesJ9(JDIDebugTarget target, List<String> qualifiedNames) |
| throws DebugException { |
| String[] typeNames = qualifiedNames |
| .toArray(new String[qualifiedNames.size()]); |
| if (target.supportsJ9HotCodeReplace()) { |
| target.setHCROccurred(true); |
| org.eclipse.jdi.hcr.VirtualMachine vm = (org.eclipse.jdi.hcr.VirtualMachine) target |
| .getVM(); |
| if (vm == null) { |
| target.requestFailed( |
| JDIDebugHCRMessages.JavaHotCodeReplaceManager_Hot_code_replace_failed___VM_disconnected__1, |
| null); |
| } |
| int result = org.eclipse.jdi.hcr.VirtualMachine.RELOAD_FAILURE; |
| try { |
| result = vm.classesHaveChanged(typeNames); |
| } catch (RuntimeException e) { |
| target.targetRequestFailed( |
| MessageFormat.format(JDIDebugHCRMessages.JavaHotCodeReplaceManager_exception_replacing_types, e.toString()), |
| e); |
| } |
| switch (result) { |
| case org.eclipse.jdi.hcr.VirtualMachine.RELOAD_SUCCESS: |
| break; |
| case org.eclipse.jdi.hcr.VirtualMachine.RELOAD_IGNORED: |
| target.targetRequestFailed( |
| JDIDebugHCRMessages.JavaHotCodeReplaceManager_hcr_ignored, |
| null); |
| break; |
| case org.eclipse.jdi.hcr.VirtualMachine.RELOAD_FAILURE: |
| target.targetRequestFailed( |
| JDIDebugHCRMessages.JavaHotCodeReplaceManager_hcr_failed, |
| null); |
| target.addOutOfSynchTypes(qualifiedNames); |
| break; |
| } |
| } else { |
| target.notSupported(JDIDebugHCRMessages.JavaHotCodeReplaceManager_does_not_support_hcr); |
| target.addOutOfSynchTypes(qualifiedNames); |
| } |
| } |
| |
| /** |
| * Replaces the given types in the given JDK-compliant debug target. |
| * |
| * This method is to be used for JDK hot code replace. |
| */ |
| private void redefineTypesJDK(JDIDebugTarget target, List<IResource> resources, |
| List<String> qualifiedNames) throws DebugException { |
| if (target.supportsJDKHotCodeReplace()) { |
| target.setHCROccurred(true); |
| Map<ReferenceType, byte[]> typesToBytes = getTypesToBytes(target, resources, |
| qualifiedNames); |
| try { |
| VirtualMachine vm = target.getVM(); |
| if (vm == null) { |
| target.requestFailed( |
| JDIDebugHCRMessages.JavaHotCodeReplaceManager_Hot_code_replace_failed___VM_disconnected__2, |
| null); |
| } |
| vm.redefineClasses(typesToBytes); |
| } catch (UnsupportedOperationException exception) { |
| String detail = exception.getMessage(); |
| if (detail != null) { |
| redefineTypesFailedJDK( |
| target, |
| qualifiedNames, |
| MessageFormat.format( |
| JDIDebugHCRMessages.JavaHotCodeReplaceManager_hcr_unsupported_operation, |
| detail), |
| exception); |
| } else { |
| redefineTypesFailedJDK( |
| target, |
| qualifiedNames, |
| JDIDebugHCRMessages.JavaHotCodeReplaceManager_hcr_unsupported_redefinition, |
| exception); |
| } |
| } catch (NoClassDefFoundError exception) { |
| redefineTypesFailedJDK( |
| target, |
| qualifiedNames, |
| JDIDebugHCRMessages.JavaHotCodeReplaceManager_hcr_bad_bytes, |
| exception); |
| } catch (VerifyError exception) { |
| redefineTypesFailedJDK( |
| target, |
| qualifiedNames, |
| JDIDebugHCRMessages.JavaHotCodeReplaceManager_hcr_verify_error, |
| exception); |
| } catch (UnsupportedClassVersionError exception) { |
| redefineTypesFailedJDK( |
| target, |
| qualifiedNames, |
| JDIDebugHCRMessages.JavaHotCodeReplaceManager_hcr_unsupported_class_version, |
| exception); |
| } catch (ClassFormatError exception) { |
| redefineTypesFailedJDK( |
| target, |
| qualifiedNames, |
| JDIDebugHCRMessages.JavaHotCodeReplaceManager_hcr_class_format_error, |
| exception); |
| } catch (ClassCircularityError exception) { |
| redefineTypesFailedJDK( |
| target, |
| qualifiedNames, |
| JDIDebugHCRMessages.JavaHotCodeReplaceManager_hcr_class_circularity_error, |
| exception); |
| } catch (RuntimeException exception) { |
| redefineTypesFailedJDK( |
| target, |
| qualifiedNames, |
| JDIDebugHCRMessages.JavaHotCodeReplaceManager_hcr_failed, |
| exception); |
| } |
| target.reinstallBreakpointsIn(resources, qualifiedNames); |
| } else { |
| target.notSupported(JDIDebugHCRMessages.JavaHotCodeReplaceManager_does_not_support_hcr); |
| } |
| } |
| |
| /** |
| * Error handling for JDK hot code replace. |
| * |
| * The given exception occurred when redefinition was attempted for the |
| * given types. |
| */ |
| private void redefineTypesFailedJDK(JDIDebugTarget target, |
| List<String> qualifiedNames, String message, Throwable exception) |
| throws DebugException { |
| target.addOutOfSynchTypes(qualifiedNames); |
| target.jdiRequestFailed(message, exception); |
| } |
| |
| /** |
| * Returns a mapping of class files to the bytes that make up those class |
| * files. |
| * |
| * @param target |
| * the debug target to query |
| * @param resources |
| * the classfiles |
| * @param qualifiedNames |
| * the fully qualified type names corresponding to the |
| * classfiles. The typeNames correspond to the resources on a |
| * one-to-one basis. |
| * @return a mapping of class files to bytes key: class file value: the |
| * bytes which make up that classfile |
| */ |
| private Map<ReferenceType, byte[]> getTypesToBytes(JDIDebugTarget target, List<IResource> resources, |
| List<String> qualifiedNames) { |
| Map<ReferenceType, byte[]> typesToBytes = new HashMap<>(resources.size()); |
| Iterator<IResource> resourceIter = resources.iterator(); |
| Iterator<String> nameIter = qualifiedNames.iterator(); |
| IResource resource; |
| String name; |
| while (resourceIter.hasNext()) { |
| resource = resourceIter.next(); |
| name = nameIter.next(); |
| List<ReferenceType> classes = target.jdiClassesByName(name); |
| byte[] bytes = null; |
| try { |
| bytes = Util.getResourceContentsAsByteArray((IFile) resource); |
| } catch (JavaModelException jme) { |
| continue; |
| } |
| for(ReferenceType type : classes) { |
| typesToBytes.put(type, bytes); |
| } |
| } |
| return typesToBytes; |
| } |
| |
| /** |
| * Return the listeners to notify for the given target. Target specific |
| * listeners take precedence over generic listeners registered with the |
| * debug model plug-in. |
| * |
| * @param target |
| * Java debug target |
| * @return hot code replace listeners |
| */ |
| private ListenerList<IJavaHotCodeReplaceListener> getHotCodeReplaceListeners(IJavaDebugTarget target) { |
| ListenerList<IJavaHotCodeReplaceListener> listeners = null; |
| if (target instanceof JDIDebugTarget) { |
| listeners = ((JDIDebugTarget) target).getHotCodeReplaceListeners(); |
| } |
| if (listeners == null || listeners.size() == 0) { |
| listeners = fHotCodeReplaceListeners; |
| } |
| return listeners; |
| } |
| |
| /** |
| * Notifies listeners that a hot code replace attempt succeeded |
| */ |
| private void fireHCRSucceeded(IJavaDebugTarget target) { |
| ListenerList<IJavaHotCodeReplaceListener> listeners = getHotCodeReplaceListeners(target); |
| for (IJavaHotCodeReplaceListener listener : listeners) { |
| listener.hotCodeReplaceSucceeded(target); |
| } |
| } |
| |
| /** |
| * Notifies listeners that a hot code replace attempt failed with the given |
| * exception |
| */ |
| private void fireHCRFailed(JDIDebugTarget target, DebugException exception) { |
| ListenerList<IJavaHotCodeReplaceListener> listeners = getHotCodeReplaceListeners(target); |
| for (IJavaHotCodeReplaceListener listener : listeners) { |
| listener.hotCodeReplaceFailed(target, exception); |
| } |
| } |
| |
| /** |
| * Notifies listeners that obsolete methods remain on the stack |
| */ |
| private void fireObsoleteMethods(JDIDebugTarget target) { |
| ListenerList<IJavaHotCodeReplaceListener> listeners = getHotCodeReplaceListeners(target); |
| for (IJavaHotCodeReplaceListener listener : listeners) { |
| listener.obsoleteMethods(target); |
| } |
| } |
| |
| /** |
| * Looks for the deepest affected stack frame in the stack and forces a drop |
| * to frame. Does this for all of the active stack frames in the target. |
| * |
| * @param target |
| * the debug target in which frames are to be dropped |
| * @param replacedClassNames |
| * the classes that have been redefined |
| */ |
| protected void attemptDropToFrame(JDIDebugTarget target, List<IResource> resources, |
| List<String> replacedClassNames) throws DebugException { |
| List<JDIStackFrame> dropFrames = getAffectedFrames(target.getThreads(), resources, |
| replacedClassNames); |
| |
| // All threads that want to drop to frame are able. Proceed with the |
| // drop |
| JDIStackFrame dropFrame = null; |
| Iterator<JDIStackFrame> iter = dropFrames.iterator(); |
| while (iter.hasNext()) { |
| try { |
| dropFrame = iter.next(); |
| dropFrame.dropToFrame(); |
| } catch (DebugException de) { |
| notifyFailedDrop( |
| ((JDIThread) dropFrame.getThread()) |
| .computeStackFrames(), |
| replacedClassNames); |
| } |
| } |
| } |
| |
| /** |
| * Looks for the deepest affected stack frame in the stack and forces a drop |
| * to frame. Does this for all of the active stack frames in the target. |
| * |
| * @param target |
| * the debug target in which frames are to be dropped |
| * @param replacedClassNames |
| * the classes that have been redefined |
| * @param poppedThreads |
| * a list of the threads in which frames were popped.This |
| * parameter may have entries added by this method |
| */ |
| protected void attemptPopFrames(JDIDebugTarget target, List<IResource> resources, |
| List<String> replacedClassNames, List<IThread> poppedThreads) throws DebugException { |
| List<JDIStackFrame> popFrames = getAffectedFrames(target.getThreads(), resources, |
| replacedClassNames); |
| |
| // All threads that want to drop to frame are able. Proceed with the |
| // drop |
| JDIStackFrame popFrame = null; |
| Iterator<JDIStackFrame> iter = popFrames.iterator(); |
| while (iter.hasNext()) { |
| try { |
| popFrame = iter.next(); |
| popFrame.popFrame(); |
| poppedThreads.add(popFrame.getThread()); |
| } catch (DebugException de) { |
| poppedThreads.remove(popFrame.getThread()); |
| notifyFailedDrop( |
| ((JDIThread) popFrame.getThread()).computeStackFrames(), |
| replacedClassNames); |
| } |
| } |
| } |
| |
| /** |
| * Returns whether or not the given target contains stack frames with |
| * obsolete methods. |
| */ |
| protected boolean containsObsoleteMethods(JDIDebugTarget target) |
| throws DebugException { |
| IThread[] threads = target.getThreads(); |
| List<IJavaStackFrame> frames = null; |
| for (IThread thread : threads) { |
| frames = ((JDIThread) thread).computeNewStackFrames(); |
| for(IJavaStackFrame frame : frames) { |
| if(frame.isObsolete()) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Returns a list of frames which should be popped in the given threads. |
| */ |
| protected List<JDIStackFrame> getAffectedFrames(IThread[] threads, List<IResource> resourceList, |
| List<String> replacedClassNames) throws DebugException { |
| JDIThread thread = null; |
| JDIStackFrame affectedFrame = null; |
| List<JDIStackFrame> popFrames = new ArrayList<>(); |
| int numThreads = threads.length; |
| IResource[] resources = new IResource[resourceList.size()]; |
| resourceList.toArray(resources); |
| for (int i = 0; i < numThreads; i++) { |
| thread = (JDIThread) threads[i]; |
| if (thread.isSuspended()) { |
| affectedFrame = getAffectedFrame(thread, replacedClassNames); |
| if (affectedFrame == null) { |
| // No frame to drop to in this thread |
| continue; |
| } |
| if (affectedFrame.supportsDropToFrame()) { |
| popFrames.add(affectedFrame); |
| } else { |
| // if any thread that should drop does not support the drop, |
| // do not drop in any threads. |
| for (int j = 0; j < numThreads; j++) { |
| notifyFailedDrop( |
| ((JDIThread) threads[i]).computeStackFrames(), |
| replacedClassNames); |
| } |
| throw new DebugException( |
| new Status( |
| IStatus.ERROR, |
| JDIDebugModel.getPluginIdentifier(), |
| DebugException.NOT_SUPPORTED, |
| JDIDebugHCRMessages.JavaHotCodeReplaceManager_Drop_to_frame_not_supported, |
| null)); |
| } |
| } |
| } |
| return popFrames; |
| } |
| |
| /** |
| * Returns the stack frame that should be dropped to in the given thread |
| * after a hot code replace. This is calculated by determining if the |
| * threads contain stack frames that reside in one of the given replaced |
| * class names. If possible, only stack frames whose methods were directly |
| * affected (and not simply all frames in affected types) will be returned. |
| */ |
| protected JDIStackFrame getAffectedFrame(JDIThread thread, |
| List<String> replacedClassNames) throws DebugException { |
| List<IJavaStackFrame> frames = thread.computeStackFrames(); |
| JDIStackFrame affectedFrame = null; |
| JDIStackFrame frame = null; |
| ICompilationUnit compilationUnit = null; |
| CompilationUnitDelta delta = null; |
| IProject project = null; |
| for (int j = 0; j < frames.size(); j++) { |
| frame = (JDIStackFrame) frames.get(j); |
| if (containsChangedType(frame, replacedClassNames)) { |
| // smart drop to frame support |
| compilationUnit = getCompilationUnit(frame); |
| // if we can't find the source, then do type-based drop |
| if (compilationUnit != null) { |
| try { |
| project = compilationUnit.getCorrespondingResource() |
| .getProject(); |
| delta = getDelta(compilationUnit, |
| getLastProjectBuildTime(project)); |
| |
| String typeName = frame.getDeclaringTypeName(); |
| typeName = typeName.replace('$', '.'); |
| |
| if (!delta.hasChanged(typeName, frame.getName(), |
| frame.getSignature())) { |
| continue; |
| } |
| } catch (CoreException exception) { |
| // If smart drop to frame fails, just do type-based drop |
| } |
| } |
| |
| if (frame.supportsDropToFrame()) { |
| affectedFrame = frame; |
| break; |
| } |
| // The frame we wanted to drop to cannot be popped. |
| // Set the affected frame to the next lowest pop-able |
| // frame on the stack. |
| while (j > 0) { |
| j--; |
| frame = (JDIStackFrame) frames.get(j); |
| if (frame.supportsDropToFrame()) { |
| affectedFrame = frame; |
| break; |
| } |
| } |
| break; |
| } |
| } |
| return affectedFrame; |
| } |
| |
| /** |
| * Returns the delta object for the given compilation unit |
| * |
| * @param cu |
| * compilation unit |
| * @param time |
| * time to compare to (i.e. compare to first version before this |
| * time) |
| * @return delta object |
| */ |
| private CompilationUnitDelta getDelta(ICompilationUnit cu, long time) |
| throws CoreException { |
| CompilationUnitDelta delta = fDeltaCache.get(cu); |
| if (delta == null) { |
| delta = new CompilationUnitDelta(cu, time); |
| fDeltaCache.put(cu, delta); |
| } |
| return delta; |
| } |
| |
| /** |
| * Returns whether the given frame's declaring type was changed based on the |
| * given list of changed class names. |
| */ |
| protected boolean containsChangedType(JDIStackFrame frame, |
| List<String> replacedClassNames) throws DebugException { |
| String declaringTypeName = frame.getDeclaringTypeName(); |
| // Check if the frame's declaring type was changed |
| if (replacedClassNames.contains(declaringTypeName)) { |
| return true; |
| } |
| // Check if one of the frame's declaring type's inner classes have |
| // changed |
| Iterator<String> iter = replacedClassNames.iterator(); |
| int index; |
| String className = null; |
| while (iter.hasNext()) { |
| className = iter.next(); |
| index = className.indexOf('$'); |
| if (index > -1 |
| && declaringTypeName.equals(className.substring(0, index))) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Performs a "step into" operation on the given threads. |
| */ |
| protected void attemptStepIn(List<IThread> threads) throws DebugException { |
| Iterator<IThread> iter = threads.iterator(); |
| while (iter.hasNext()) { |
| ((JDIThread) iter.next()).stepInto(); |
| } |
| } |
| |
| /** |
| * Returns the compilation unit associated with this Java stack frame. |
| * Returns <code>null</code> for a binary stack frame. |
| */ |
| protected ICompilationUnit getCompilationUnit(IJavaStackFrame frame) { |
| ILaunch launch = frame.getLaunch(); |
| if (launch == null) { |
| return null; |
| } |
| try { |
| IJavaElement sourceElement = JavaDebugUtils.resolveJavaElement(frame, launch); |
| if (sourceElement instanceof IType) { |
| return ((IType) sourceElement).getCompilationUnit(); |
| } |
| if (sourceElement instanceof ICompilationUnit) { |
| return (ICompilationUnit) sourceElement; |
| } |
| return null; |
| } |
| catch (CoreException e) { |
| return null; |
| } |
| } |
| |
| /** |
| * Returns the method in which this stack frame is suspended or |
| * <code>null</code> if none can be found |
| */ |
| public IMethod getMethod(JDIStackFrame frame, ICompilationUnit unit) |
| throws CoreException { |
| String declaringTypeName = frame.getDeclaringTypeName(); |
| String methodName = frame.getMethodName(); |
| String[] arguments = null; |
| try { |
| arguments = Signature.getParameterTypes(frame.getSignature()); |
| } catch (IllegalArgumentException exception) { |
| // If Signature can't parse the signature, we can't |
| // create the method |
| return null; |
| } |
| String typeName = getUnqualifiedName(declaringTypeName); |
| int index = typeName.indexOf('$'); |
| IType type = null; |
| if (index > 0) { |
| String remaining = typeName.substring(index + 1); |
| typeName = typeName.substring(0, index); |
| type = unit.getType(typeName); |
| while (remaining != null) { |
| index = remaining.indexOf('$'); |
| if (index > 0) { |
| typeName = remaining.substring(0, index); |
| remaining = remaining.substring(index + 1); |
| } else { |
| typeName = remaining; |
| remaining = null; |
| } |
| type = type.getType(typeName); |
| } |
| } else { |
| type = unit.getType(typeName); |
| } |
| if (type != null) { |
| return type.getMethod(methodName, arguments); |
| } |
| return null; |
| } |
| |
| /** |
| * Given a fully qualified name, return the unqualified name. |
| */ |
| protected String getUnqualifiedName(String qualifiedName) { |
| int index = qualifiedName.lastIndexOf('.'); |
| return qualifiedName.substring(index + 1); |
| } |
| |
| /** |
| * Notify the given frames that a drop to frame has failed after an HCR with |
| * the given class names. |
| */ |
| private void notifyFailedDrop(List<IJavaStackFrame> frames, List<String> replacedClassNames) |
| throws DebugException { |
| for(IJavaStackFrame frame : frames) { |
| if (replacedClassNames.contains(frame.getDeclaringTypeName())) { |
| ((JDIStackFrame)frame).setOutOfSynch(true); |
| } |
| } |
| } |
| |
| /** |
| * Returns the class file visitor after visiting the resource change. The |
| * visitor contains the changed class files and qualified type names. |
| * Returns <code>null</code> if the visitor encounters an exception, or the |
| * delta is not a POST_BUILD. |
| */ |
| protected ChangedClassFilesVisitor getChangedClassFiles( |
| IResourceChangeEvent event) { |
| IResourceDelta delta = event.getDelta(); |
| if (event.getType() != IResourceChangeEvent.POST_BUILD || delta == null) { |
| return null; |
| } |
| fClassfileVisitor.reset(); |
| try { |
| delta.accept(fClassfileVisitor); |
| } catch (CoreException e) { |
| JDIDebugPlugin.log(e); |
| return null; // quiet failure |
| } |
| return fClassfileVisitor; |
| } |
| |
| /** |
| * A visitor which collects changed class files. |
| */ |
| class ChangedClassFilesVisitor implements IResourceDeltaVisitor { |
| /** |
| * The collection of changed class files. |
| */ |
| protected List<IResource> fFiles = null; |
| |
| /** |
| * Collection of qualified type names, corresponding to class files. |
| */ |
| protected List<String> fNames = null; |
| |
| /** |
| * Answers whether children should be visited. |
| * <p> |
| * If the associated resource is a class file which has been changed, |
| * record it. |
| */ |
| @Override |
| public boolean visit(IResourceDelta delta) { |
| if (delta == null |
| || 0 == (delta.getKind() & IResourceDelta.CHANGED)) { |
| return false; |
| } |
| IResource resource = delta.getResource(); |
| if (resource != null) { |
| switch (resource.getType()) { |
| case IResource.FILE: |
| if (0 == (delta.getFlags() & IResourceDelta.CONTENT)) { |
| return false; |
| } |
| if (CLASS_FILE_EXTENSION.equals(resource.getFullPath() |
| .getFileExtension())) { |
| IPath localLocation = resource.getLocation(); |
| if (localLocation != null) { |
| String path = localLocation.toOSString(); |
| IClassFileReader reader = ToolFactory |
| .createDefaultClassFileReader( |
| path, |
| IClassFileReader.CLASSFILE_ATTRIBUTES); |
| if (reader != null) { |
| // this name is slash-delimited |
| String qualifiedName = new String( |
| reader.getClassName()); |
| boolean hasBlockingErrors = false; |
| try { |
| if (!Platform.getPreferencesService().getBoolean( |
| JDIDebugPlugin.getUniqueIdentifier(), |
| JDIDebugModel.PREF_HCR_WITH_COMPILATION_ERRORS, |
| true, |
| null)) { |
| // If the user doesn't want to replace |
| // classfiles containing |
| // compilation errors, get the source |
| // file associated with |
| // the class file and query it for |
| // compilation errors |
| IJavaProject pro = JavaCore |
| .create(resource.getProject()); |
| ISourceAttribute sourceAttribute = reader |
| .getSourceFileAttribute(); |
| String sourceName = null; |
| if (sourceAttribute != null) { |
| sourceName = new String( |
| sourceAttribute |
| .getSourceFileName()); |
| } |
| IResource sourceFile = getSourceFile( |
| pro, qualifiedName, sourceName); |
| if (sourceFile != null) { |
| IMarker[] problemMarkers = null; |
| problemMarkers = sourceFile |
| .findMarkers( |
| IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, |
| true, |
| IResource.DEPTH_INFINITE); |
| for (IMarker problemMarker : problemMarkers) { |
| if (problemMarker.getAttribute( |
| IMarker.SEVERITY, -1) == IMarker.SEVERITY_ERROR) { |
| hasBlockingErrors = true; |
| break; |
| } |
| } |
| } |
| } |
| } catch (CoreException e) { |
| JDIDebugPlugin.log(e); |
| } |
| if (!hasBlockingErrors) { |
| fFiles.add(resource); |
| // dot-delimit the name |
| fNames.add(qualifiedName.replace('/', '.')); |
| } |
| } |
| } |
| } |
| return false; |
| |
| default: |
| return true; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Resets the file collection to empty |
| */ |
| public void reset() { |
| fFiles = new ArrayList<>(); |
| fNames = new ArrayList<>(); |
| } |
| |
| /** |
| * Answers a collection of changed class files or <code>null</code> |
| */ |
| public List<IResource> getChangedClassFiles() { |
| return fFiles; |
| } |
| |
| /** |
| * Returns a collection of qualified type names corresponding to the |
| * changed class files. |
| * |
| * @return List |
| */ |
| public List<String> getQualifiedNamesList() { |
| return fNames; |
| } |
| |
| /** |
| * Returns the source file associated with the given type, or |
| * <code>null</code> if no source file could be found. |
| * |
| * @param project |
| * the java project containing the classfile |
| * @param qualifiedName |
| * fully qualified name of the type, slash delimited |
| * @param sourceAttribute |
| * debug source attribute, or <code>null</code> if none |
| */ |
| private IResource getSourceFile(IJavaProject project, |
| String qualifiedName, String sourceAttribute) { |
| String name = null; |
| IJavaElement element = null; |
| try { |
| if (sourceAttribute == null) { |
| element = JavaDebugUtils |
| .findElement(qualifiedName, project); |
| } else { |
| int i = qualifiedName.lastIndexOf('/'); |
| if (i > 0) { |
| name = qualifiedName.substring(0, i + 1); |
| name = name + sourceAttribute; |
| } else { |
| name = sourceAttribute; |
| } |
| element = project.findElement(new Path(name)); |
| } |
| if (element instanceof ICompilationUnit) { |
| ICompilationUnit cu = (ICompilationUnit) element; |
| return cu.getCorrespondingResource(); |
| } |
| } catch (CoreException e) { |
| } |
| return null; |
| } |
| } |
| |
| /** |
| * Adds the given listener to the collection of hot code replace listeners. |
| * Listeners are notified when hot code replace attempts succeed or fail. |
| */ |
| public void addHotCodeReplaceListener(IJavaHotCodeReplaceListener listener) { |
| fHotCodeReplaceListeners.add(listener); |
| } |
| |
| /** |
| * Removes the given listener from the collection of hot code replace |
| * listeners. Once a listener is removed, it will no longer be notified of |
| * hot code replace attempt successes or failures. |
| */ |
| public void removeHotCodeReplaceListener( |
| IJavaHotCodeReplaceListener listener) { |
| fHotCodeReplaceListeners.remove(listener); |
| } |
| |
| /** |
| * @see ILaunchListener#launchRemoved(ILaunch) |
| */ |
| @Override |
| public void launchRemoved(ILaunch launch) { |
| IDebugTarget[] debugTargets = launch.getDebugTargets(); |
| for (IDebugTarget debugTarget : debugTargets) { |
| IJavaDebugTarget jt = debugTarget |
| .getAdapter(IJavaDebugTarget.class); |
| if (jt != null) { |
| deregisterTarget((JDIDebugTarget) jt); |
| } |
| } |
| } |
| |
| /** |
| * Begin listening for resource changes when a launch is registered with a |
| * hot swap-able target. |
| * |
| * @see org.eclipse.debug.core.ILaunchListener#launchAdded(org.eclipse.debug.core.ILaunch) |
| */ |
| @Override |
| public void launchAdded(ILaunch launch) { |
| IDebugTarget[] debugTargets = launch.getDebugTargets(); |
| for (IDebugTarget debugTarget : debugTargets) { |
| IJavaDebugTarget jt = debugTarget |
| .getAdapter(IJavaDebugTarget.class); |
| if (jt != null) { |
| JDIDebugTarget target = (JDIDebugTarget) jt; |
| if (target.supportsHotCodeReplace()) { |
| addHotSwapTarget(target); |
| } else if (target.isAvailable()) { |
| addNonHotSwapTarget(target); |
| } |
| } |
| } |
| synchronized (this) { |
| if (!fHotSwapTargets.isEmpty() || !fNoHotSwapTargets.isEmpty()) { |
| getWorkspace().addResourceChangeListener(this, |
| IResourceChangeEvent.POST_BUILD); |
| } |
| } |
| } |
| |
| /** |
| * Begin listening for resource changes when a launch is registered with a |
| * hot swap-able target. |
| * |
| * @see ILaunchListener#launchChanged(ILaunch) |
| */ |
| @Override |
| public void launchChanged(ILaunch launch) { |
| launchAdded(launch); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see |
| * org.eclipse.debug.core.IDebugEventSetListener#handleDebugEvents(org.eclipse |
| * .debug.core.DebugEvent[]) |
| */ |
| @Override |
| public void handleDebugEvents(DebugEvent[] events) { |
| for (DebugEvent event : events) { |
| if (event.getKind() == DebugEvent.TERMINATE) { |
| Object source = event.getSource(); |
| if (source instanceof IAdaptable |
| && source instanceof IDebugTarget) { |
| IJavaDebugTarget jt = ((IAdaptable) source) |
| .getAdapter(IJavaDebugTarget.class); |
| if (jt != null) { |
| deregisterTarget((JDIDebugTarget) jt); |
| } |
| } |
| } |
| } |
| } |
| |
| protected void deregisterTarget(JDIDebugTarget target) { |
| // Remove the target from its hot swap target cache. |
| if (!fHotSwapTargets.remove(target)) { |
| fNoHotSwapTargets.remove(target); |
| } |
| ILaunch[] launches = DebugPlugin.getDefault().getLaunchManager() |
| .getLaunches(); |
| // If there are no more active JDIDebugTargets, stop |
| // listening to resource changes. |
| for (ILaunch launche : launches) { |
| IDebugTarget[] targets = launche.getDebugTargets(); |
| for (IDebugTarget debugTarget : targets) { |
| IJavaDebugTarget jt = debugTarget |
| .getAdapter(IJavaDebugTarget.class); |
| if (jt != null) { |
| if (((JDIDebugTarget) jt).isAvailable()) { |
| return; |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Adds the given target to the list of hot-swap-able targets. Has no effect |
| * if the target is already registered. |
| * |
| * @param target |
| * a target that supports hot swap |
| */ |
| protected synchronized void addHotSwapTarget(JDIDebugTarget target) { |
| if (!fHotSwapTargets.contains(target)) { |
| fHotSwapTargets.add(target); |
| } |
| } |
| |
| /** |
| * Adds the given target to the list of non hot-swap-able targets. Has no |
| * effect if the target is already registered. |
| * |
| * @param target |
| * a target that does not support hot swap |
| */ |
| protected synchronized void addNonHotSwapTarget(JDIDebugTarget target) { |
| if (!fNoHotSwapTargets.contains(target)) { |
| fNoHotSwapTargets.add(target); |
| } |
| } |
| |
| } |