blob: b9f79ef7e4b44e662478dbc81b4d5e3b036e9863 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2003 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.debug.core.hcr;
import java.text.MessageFormat;
import java.util.ArrayList;
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.IWorkspaceRunnable;
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.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Path;
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.ISourceLocator;
import org.eclipse.debug.core.model.IThread;
import org.eclipse.debug.internal.core.ListenerList;
import org.eclipse.jdt.core.ICompilationUnit;
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.JDIDebugModel;
import org.eclipse.jdt.internal.core.util.Util;
import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin;
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.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 fHotCodeReplaceListeners= new ListenerList(1);
/**
* The lists of hot swap targets which support HCR and those which don't
*/
private ArrayList fHotSwapTargets= new ArrayList(1);
private ArrayList 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 fProjectBuildTimes= new HashMap();
private static Date fStartupDate= new Date();
/**
* 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;
}
}
protected BuiltProjectVisitor fProjectVisitor= new BuiltProjectVisitor();
/**
* 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 plugin on startup.
*/
public void startup() {
DebugPlugin.getDefault().getLaunchManager().addLaunchListener(this);
DebugPlugin.getDefault().addDebugEventListener(this);
}
/**
* Deregisters this HCR manager as a resource change listener. Removes all hot
* code replace listeners. This method is called by the JDI debug model plugin
* on shutdown.
*/
public void shutdown() {
DebugPlugin.getDefault().getLaunchManager().removeLaunchListener(this);
DebugPlugin.getDefault().removeDebugEventListener(this);
getWorkspace().removeResourceChangeListener(this);
fHotCodeReplaceListeners.removeAll();
fHotSwapTargets= null;
fNoHotSwapTargets= null;
}
/**
* 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)
*/
public void resourceChanged(IResourceChangeEvent event) {
List projects= getBuiltProjects(event);
if (!projects.isEmpty()) {
updateProjectBuildTime(projects);
}
ChangedClassFilesVisitor visitor = getChangedClassFiles(event);
if (visitor != null) {
List resources = visitor.getChangedClassFiles();
List names = visitor.getQualifiedNamesList();
if (!resources.isEmpty()) {
notifyTargets(resources, names);
}
}
}
/**
* Returns all projects which this event says may have been built.
*/
protected List getBuiltProjects(IResourceChangeEvent event) {
IResourceDelta delta= event.getDelta();
if (event.getType() != IResourceChangeEvent.POST_AUTO_BUILD || delta == null) {
return Collections.EMPTY_LIST;
}
fProjectVisitor.reset();
try {
delta.accept(fProjectVisitor);
} catch (CoreException e) {
JDIDebugPlugin.log(e);
return Collections.EMPTY_LIST;
}
return fProjectVisitor.getBuiltProjects();
}
/**
* If the given event contains a build notification, update the
* last build time of the corresponding project
*/
private void updateProjectBuildTime(List projects) {
Iterator iter= projects.iterator();
IProject project= null;
Date currentDate= new Date();
ProjectBuildTime buildTime= null;
while (iter.hasNext()) {
project= (IProject) iter.next();
buildTime= (ProjectBuildTime)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= (ProjectBuildTime)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 resources, final List qualifiedNames) {
final List hotSwapTargets= getHotSwapTargets();
final List noHotSwapTargets= getNoHotSwapTargets();
if (!hotSwapTargets.isEmpty()) {
IWorkspaceRunnable wRunnable= new IWorkspaceRunnable() {
public void run(IProgressMonitor monitor) {
doHotCodeReplace(hotSwapTargets, resources, qualifiedNames);
}
};
fork(wRunnable);
}
if (!noHotSwapTargets.isEmpty()) {
IWorkspaceRunnable wRunnable= new IWorkspaceRunnable() {
public void run(IProgressMonitor monitor) {
notifyUnsupportedHCR(noHotSwapTargets, qualifiedNames);
}
};
fork(wRunnable);
}
}
/**
* Notify the given targets that HCR failed for classes
* with the given fully qualified names.
*/
protected void notifyUnsupportedHCR(List targets, List qualifiedNames) {
Iterator iter= targets.iterator();
JDIDebugTarget target= null;
while (iter.hasNext()) {
target= (JDIDebugTarget) iter.next();
fireHCRFailed(target, null);
notifyFailedHCR(target, qualifiedNames);
}
}
protected void notifyFailedHCR(JDIDebugTarget target, List qualifiedNames) {
if (target.isAvailable()) {
target.addOutOfSynchTypes(qualifiedNames);
target.fireChangeEvent(DebugEvent.STATE);
}
}
/**
* Returns the currently registered debug targets that support
* hot code replace.
*/
protected List getHotSwapTargets() {
return (List) fHotSwapTargets.clone();
}
/**
* Returns the currently registered debug targets that do
* not support hot code replace.
*/
protected List getNoHotSwapTargets() {
return (List) fNoHotSwapTargets.clone();
}
/**
* 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 targets, List resources, List qualifiedNames) {
MultiStatus ms= new MultiStatus(JDIDebugPlugin.getUniqueIdentifier(), DebugException.TARGET_REQUEST_FAILED, JDIDebugHCRMessages.getString("JavaHotCodeReplaceManager.drop_to_frame_failed"), null); //$NON-NLS-1$
Iterator iter= targets.iterator();
while (iter.hasNext()) {
JDIDebugTarget target= (JDIDebugTarget) iter.next();
if (!target.isAvailable()) {
continue;
}
List 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, resources, qualifiedNames, poppedThreads);
framesPopped= true; // No exception occurred
} catch (DebugException de) {
ms.merge(de.getStatus());
}
}
target.removeOutOfSynchTypes(qualifiedNames);
if (target.supportsJDKHotCodeReplace()) {
redefineTypesJDK(target, resources, qualifiedNames);
} else if (target.supportsJ9HotCodeReplace()) {
redefineTypesJ9(target, qualifiedNames);
}
if (containsObsoleteMethods(target)) {
fireObsoleteMethods(target);
}
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.
try {
// must re-set 'is doing HCR' to be able to step
target.setIsPerformingHotCodeReplace(false);
attemptStepIn(poppedThreads);
} catch (DebugException de) {
ms.merge(de.getStatus());
}
} else {
// J9 drop to frame support:
// After redefining classes, drop to frame
attemptDropToFrame(target, resources, qualifiedNames);
}
fireHCRSucceeded(target);
} catch (DebugException de) {
// target update failed
fireHCRFailed(target, de);
}
// also re-set 'is doing HCR' here incase HCR failed
target.setIsPerformingHotCodeReplace(false);
target.fireChangeEvent(DebugEvent.CONTENT);
}
if (!ms.isOK()) {
JDIDebugPlugin.log(ms);
}
}
/**
* 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 qualifiedNames) throws DebugException {
String[] typeNames = (String[]) 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.getString("JavaHotCodeReplaceManager.Hot_code_replace_failed_-_VM_disconnected._1"), null); //$NON-NLS-1$
}
int result= org.eclipse.jdi.hcr.VirtualMachine.RELOAD_FAILURE;
try {
result= vm.classesHaveChanged(typeNames);
} catch (RuntimeException e) {
target.targetRequestFailed(MessageFormat.format(JDIDebugHCRMessages.getString("JavaHotCodeReplaceManager.exception_replacing_types"), new String[] {e.toString()}), e); //$NON-NLS-1$
}
switch (result) {
case org.eclipse.jdi.hcr.VirtualMachine.RELOAD_SUCCESS:
break;
case org.eclipse.jdi.hcr.VirtualMachine.RELOAD_IGNORED:
target.targetRequestFailed(JDIDebugHCRMessages.getString("JavaHotCodeReplaceManager.hcr_ignored"), null); //$NON-NLS-1$
break;
case org.eclipse.jdi.hcr.VirtualMachine.RELOAD_FAILURE:
target.targetRequestFailed(JDIDebugHCRMessages.getString("JavaHotCodeReplaceManager.hcr_failed"), null); //$NON-NLS-1$
target.addOutOfSynchTypes(qualifiedNames);
break;
}
} else {
target.notSupported(JDIDebugHCRMessages.getString("JavaHotCodeReplaceManager.does_not_support_hcr")); //$NON-NLS-1$
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 resources, List qualifiedNames) throws DebugException {
if (target.supportsJDKHotCodeReplace()) {
target.setHCROccurred(true);
Map typesToBytes= getTypesToBytes(target, resources, qualifiedNames);
try {
VirtualMachine vm = target.getVM();
if (vm == null) {
target.requestFailed(JDIDebugHCRMessages.getString("JavaHotCodeReplaceManager.Hot_code_replace_failed_-_VM_disconnected._2"), null); //$NON-NLS-1$
}
vm.redefineClasses(typesToBytes);
} catch (UnsupportedOperationException exception) {
String detail= exception.getMessage();
if (detail != null) {
redefineTypesFailedJDK(target, qualifiedNames, MessageFormat.format(JDIDebugHCRMessages.getString("JavaHotCodeReplaceManager.hcr_unsupported_operation"), new String[] {detail}), exception); //$NON-NLS-1$
} else {
redefineTypesFailedJDK(target, qualifiedNames, JDIDebugHCRMessages.getString("JavaHotCodeReplaceManager.hcr_unsupported_redefinition"), exception); //$NON-NLS-1$
}
} catch (NoClassDefFoundError exception) {
redefineTypesFailedJDK(target, qualifiedNames, JDIDebugHCRMessages.getString("JavaHotCodeReplaceManager.hcr_bad_bytes"), exception); //$NON-NLS-1$
} catch (VerifyError exception) {
redefineTypesFailedJDK(target, qualifiedNames, JDIDebugHCRMessages.getString("JavaHotCodeReplaceManager.hcr_verify_error"), exception); //$NON-NLS-1$
} catch (UnsupportedClassVersionError exception) {
redefineTypesFailedJDK(target, qualifiedNames, JDIDebugHCRMessages.getString("JavaHotCodeReplaceManager.hcr_unsupported_class_version"), exception); //$NON-NLS-1$
} catch (ClassFormatError exception) {
redefineTypesFailedJDK(target, qualifiedNames, JDIDebugHCRMessages.getString("JavaHotCodeReplaceManager.hcr_class_format_error"), exception); //$NON-NLS-1$
} catch (ClassCircularityError exception) {
redefineTypesFailedJDK(target, qualifiedNames, JDIDebugHCRMessages.getString("JavaHotCodeReplaceManager.hcr_class_circularity_error"), exception); //$NON-NLS-1$
} catch (RuntimeException exception) {
redefineTypesFailedJDK(target, qualifiedNames, JDIDebugHCRMessages.getString("JavaHotCodeReplaceManager.hcr_failed"), exception); //$NON-NLS-1$
}
target.reinstallBreakpointsIn(resources, qualifiedNames);
} else {
target.notSupported(JDIDebugHCRMessages.getString("JavaHotCodeReplaceManager.does_not_support_hcr")); //$NON-NLS-1$
}
}
/**
* 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 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 getTypesToBytes(JDIDebugTarget target, List resources, List qualifiedNames) {
Map typesToBytes= new HashMap(resources.size());
Iterator resourceIter= resources.iterator();
Iterator nameIter= qualifiedNames.iterator();
IResource resource;
String name;
while (resourceIter.hasNext()) {
resource= (IResource) resourceIter.next();
name= (String) nameIter.next();
List classes= target.jdiClassesByName(name);
byte[] bytes= null;
try {
bytes= Util.getResourceContentsAsByteArray((IFile) resource);
} catch (JavaModelException jme) {
continue;
}
Iterator classIter= classes.iterator();
while (classIter.hasNext()) {
ReferenceType type= (ReferenceType) classIter.next();
typesToBytes.put(type, bytes);
}
}
return typesToBytes;
}
/**
* Notifies listeners that a hot code replace attempt succeeded
*/
private void fireHCRSucceeded(IJavaDebugTarget target) {
Object[] listeners= fHotCodeReplaceListeners.getListeners();
for (int i=0; i<listeners.length; i++) {
((IJavaHotCodeReplaceListener)listeners[i]).hotCodeReplaceSucceeded(target);
}
}
/**
* Notifies listeners that a hot code replace attempt failed with the given exception
*/
private void fireHCRFailed(JDIDebugTarget target, DebugException exception) {
Object[] listeners= fHotCodeReplaceListeners.getListeners();
for (int i=0; i<listeners.length; i++) {
((IJavaHotCodeReplaceListener)listeners[i]).hotCodeReplaceFailed(target, exception);
}
}
/**
* Notifies listeners that obsolete methods remain on the stack
*/
private void fireObsoleteMethods(JDIDebugTarget target) {
Object[] listeners= fHotCodeReplaceListeners.getListeners();
for (int i=0; i<listeners.length; i++) {
((IJavaHotCodeReplaceListener)listeners[i]).obsoleteMethods(target);
}
}
/**
* Looks for the deepest effected 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 resources, List replacedClassNames) throws DebugException {
List dropFrames= getAffectedFrames(target.getThreads(), resources, replacedClassNames);
// All threads that want to drop to frame are able. Proceed with the drop
JDIStackFrame dropFrame= null;
Iterator iter= dropFrames.iterator();
while (iter.hasNext()) {
try {
dropFrame= ((JDIStackFrame)iter.next());
dropFrame.dropToFrame();
} catch (DebugException de) {
notifyFailedDrop(((JDIThread)dropFrame.getThread()).computeStackFrames(), replacedClassNames);
}
}
}
/**
* Looks for the deepest effected 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 resources, List replacedClassNames, List poppedThreads) throws DebugException {
List popFrames= getAffectedFrames(target.getThreads(), resources, replacedClassNames);
// All threads that want to drop to frame are able. Proceed with the drop
JDIStackFrame popFrame= null;
Iterator iter= popFrames.iterator();
while (iter.hasNext()) {
try {
popFrame= ((JDIStackFrame)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 frames= null;
Iterator iter= null;
for (int i= 0, numThreads= threads.length; i < numThreads; i++) {
frames= ((JDIThread)threads[i]).computeNewStackFrames();
iter= frames.iterator();
while (iter.hasNext()) {
if (((JDIStackFrame)iter.next()).isObsolete()) {
return true;
}
}
}
return false;
}
/**
* Returns a list of frames which should be popped in the given threads.
*/
protected List getAffectedFrames(IThread[] threads, List resourceList, List replacedClassNames) throws DebugException {
JDIThread thread= null;
JDIStackFrame affectedFrame= null;
List 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.getString("JavaHotCodeReplaceManager.Drop_to_frame_not_supported"), null)); //$NON-NLS-1$
}
}
}
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 replacedClassNames) throws DebugException {
List frames= thread.computeStackFrames();
JDIStackFrame affectedFrame= null;
JDIStackFrame frame= null;
ICompilationUnit compilationUnit= null;
IMethod method= null;
CompilationUnitDelta delta= null;
IProject project= null;
for (int j= frames.size() - 1; j >= 0; j--) {
frame= (JDIStackFrame) frames.get(j);
if (containsChangedType(frame, replacedClassNames)) {
// smart drop to frame support
compilationUnit= getCompilationUnit(frame);
if (compilationUnit == null) {
continue;
}
try {
project= compilationUnit.getCorrespondingResource().getProject();
method= getMethod(frame, compilationUnit);
if (method != null) {
delta= new CompilationUnitDelta(compilationUnit, getLastProjectBuildTime(project));
if (!delta.hasChanged(method)) {
continue;
}
}
} catch (CoreException exception) {
// If smart drop to frame fails, just do type-based drop
}
if (frame.supportsDropToFrame()) {
affectedFrame= frame;
break;
} else {
// The frame we wanted to drop to cannot be popped.
// Set the affected frame to the next lowest poppable
// frame on the stack.
while (j > 0) {
j--;
frame= (JDIStackFrame) frames.get(j);
if (frame.supportsDropToFrame()) {
affectedFrame= frame;
break;
}
}
break;
}
}
}
return affectedFrame;
}
/**
* 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 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 iter= replacedClassNames.iterator();
int index;
String className= null;
while (iter.hasNext()) {
className= (String) 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 threads) throws DebugException {
Iterator 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;
}
ISourceLocator locator= launch.getSourceLocator();
if (locator == null) {
return null;
}
Object sourceElement= locator.getSourceElement(frame);
if (sourceElement instanceof IType) {
return ((IType)sourceElement).getCompilationUnit();
}
if (sourceElement instanceof ICompilationUnit) {
return (ICompilationUnit)sourceElement;
}
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 frames, List replacedClassNames) throws DebugException {
JDIStackFrame frame;
Iterator iter= frames.iterator();
while (iter.hasNext()) {
frame= (JDIStackFrame) iter.next();
if (replacedClassNames.contains(frame.getDeclaringTypeName())) {
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 detlta is not a POST_CHANGE.
*/
protected ChangedClassFilesVisitor getChangedClassFiles(IResourceChangeEvent event) {
IResourceDelta delta= event.getDelta();
if (event.getType() != IResourceChangeEvent.POST_CHANGE || 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 fFiles= null;
/**
* Collection of qualified type names, corresponding to class files.
*/
protected List fNames= null;
/**
* Answers whether children should be visited.
* <p>
* If the associated resource is a class file which
* has been changed, record it.
*/
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 (!JDIDebugModel.getPreferences().getBoolean(JDIDebugModel.PREF_HCR_WITH_COMPILATION_ERRORS)) {
// 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 (int i= 0; i < problemMarkers.length; i++) {
if (problemMarkers[i].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 getChangedClassFiles() {
return fFiles;
}
/**
* Returns a collection of qualified type names corresponding to the
* changed class files.
*
* @return List
*/
public List 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;
if (sourceAttribute == null) {
int nestedIndex= qualifiedName.indexOf('$');
if (nestedIndex != -1) {
// Trim nested type suffix
name= qualifiedName.substring(0, nestedIndex);
}
name= name + ".java"; //$NON-NLS-1$
} else {
int i = qualifiedName.lastIndexOf('/');
if (i > 0) {
name = qualifiedName.substring(0, i + 1);
name = name + sourceAttribute;
} else {
name = sourceAttribute;
}
}
ICompilationUnit unit= null;
try {
unit= (ICompilationUnit)project.findElement(new Path(name));
if (unit != null) {
try {
return unit.getCorrespondingResource();
} catch (JavaModelException e) {
}
}
} catch (JavaModelException exception) {
}
return null;
}
}
class BuiltProjectVisitor implements IResourceDeltaVisitor {
/**
* The collection of built projects
*/
protected List fProjects= new ArrayList();
/**
* Answers whether children should be visited.
* <p>
* If the associated resource is a project which
* has been built, record it.
*/
public boolean visit(IResourceDelta delta) {
if (delta == null || 0 == (delta.getKind() & IResourceDelta.CHANGED)) {
return false;
}
IResource resource= delta.getResource();
if (resource != null && resource.getType() == IResource.PROJECT) {
fProjects.add(resource);
return false;
}
return true;
}
/**
* Resets the project collection to empty
*/
public void reset() {
fProjects = new ArrayList();
}
/**
* Returns the collection of built projects
*/
public List getBuiltProjects() {
return fProjects;
}
}
/**
* 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);
}
protected void fork(final IWorkspaceRunnable wRunnable) {
Runnable runnable= new Runnable() {
public void run() {
try {
getWorkspace().run(wRunnable, null);
} catch (CoreException ce) {
JDIDebugPlugin.log(ce);
}
}
};
new Thread(runnable).start();
}
/**
* @see ILaunchListener#launchRemoved(ILaunch)
*/
public void launchRemoved(ILaunch launch) {
IDebugTarget[] debugTargets= launch.getDebugTargets();
for (int i = 0; i < debugTargets.length; i++) {
IJavaDebugTarget jt = (IJavaDebugTarget)debugTargets[i].getAdapter(IJavaDebugTarget.class);
if (jt != null) {
deregisterTarget((JDIDebugTarget)jt);
}
}
}
/**
* Begin listening for resource changes when a launch is
* registered with a hot swapable target.
*
* @see org.eclipse.debug.core.ILaunchListener#launchAdded(org.eclipse.debug.core.ILaunch)
*/
public void launchAdded(ILaunch launch) {
IDebugTarget[] debugTargets= launch.getDebugTargets();
for (int i = 0; i < debugTargets.length; i++) {
IJavaDebugTarget jt = (IJavaDebugTarget)debugTargets[i].getAdapter(IJavaDebugTarget.class);
if (jt != null) {
JDIDebugTarget target = (JDIDebugTarget)jt;
if (target.supportsHotCodeReplace()) {
addHotSwapTarget(target);
} else {
addNonHotSwapTarget(target);
}
}
}
if (!fHotSwapTargets.isEmpty() || !fNoHotSwapTargets.isEmpty()) {
getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE | IResourceChangeEvent.POST_AUTO_BUILD);
}
}
/**
* Begin listening for resource changes when a launch is
* registered with a hot swapable target.
*
* @see ILaunchListener#launchChanged(ILaunch)
*/
public void launchChanged(ILaunch launch) {
launchAdded(launch);
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.IDebugEventSetListener#handleDebugEvents(org.eclipse.debug.core.DebugEvent[])
*/
public void handleDebugEvents(DebugEvent[] events) {
for (int i = 0; i < events.length; i++) {
DebugEvent event = events[i];
if (event.getKind() == DebugEvent.TERMINATE) {
Object source = event.getSource();
if (source instanceof IAdaptable) {
IJavaDebugTarget jt = (IJavaDebugTarget)((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 (int i= 0; i < launches.length; i++) {
IDebugTarget[] targets = launches[i].getDebugTargets();
for (int j = 0; j < targets.length; j++) {
IDebugTarget debugTarget = targets[j];
IJavaDebugTarget jt = (IJavaDebugTarget)debugTarget.getAdapter(IJavaDebugTarget.class);
if (jt != null) {
if (((JDIDebugTarget)jt).isAvailable()) {
return;
}
}
}
}
// To get here, there must be no running JDIDebugTargets
getWorkspace().removeResourceChangeListener(this);
}
/**
* Adds the given target to the list of hot-swappable targets.
* Has no effect if the target is already registered.
*
* @param target a target that supports hot swap
*/
protected void addHotSwapTarget(JDIDebugTarget target) {
if (!fHotSwapTargets.contains(target)) {
fHotSwapTargets.add(target);
}
}
/**
* Adds the given target to the list of non hot-swappable targets.
* Has no effect if the target is already registered.
*
* @param target a target that does not support hot swap
*/
protected void addNonHotSwapTarget(JDIDebugTarget target) {
if (!fNoHotSwapTargets.contains(target)) {
fNoHotSwapTargets.add(target);
}
}
}