blob: 2e9b7bf4ecbc62b9dc4d6432fe5a0d97cc84d9b8 [file] [log] [blame]
package org.eclipse.dltk.internal.debug.core.model;
import java.util.ArrayList;
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.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
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.model.IDebugTarget;
import org.eclipse.dltk.core.IDLTKLanguageToolkit;
import org.eclipse.dltk.core.PriorityClassDLTKExtensionManager;
import org.eclipse.dltk.debug.core.DLTKDebugPlugin;
import org.eclipse.dltk.debug.core.IHotCodeReplaceListener;
import org.eclipse.dltk.debug.core.IHotCodeReplaceProvider;
import org.eclipse.dltk.debug.core.model.IScriptDebugTarget;
import org.eclipse.osgi.util.NLS;
public class HotCodeReplaceManager implements IResourceChangeListener,
ILaunchListener, IDebugEventSetListener {
private static final String HOT_CODE_REPLACE_PROVIDER_EXTENSION = DLTKDebugPlugin.PLUGIN_ID
+ ".hotCodeReplaceProvider"; //$NON-NLS-1$
private static PriorityClassDLTKExtensionManager providerManager = new PriorityClassDLTKExtensionManager(
HOT_CODE_REPLACE_PROVIDER_EXTENSION, "nature"); //$NON-NLS-1$
private static HotCodeReplaceManager instance = null;
private ArrayList fHotSwapTargets = new ArrayList();
private ArrayList fNoHotSwapTargets = new ArrayList();
private ListenerList<IHotCodeReplaceListener> fHotCodeReplaceListeners = new ListenerList<>();
public static synchronized HotCodeReplaceManager getDefault() {
if (instance == null) {
instance = new HotCodeReplaceManager();
}
return instance;
}
private HotCodeReplaceManager() {
}
public void startup() {
DebugPlugin.getDefault().getLaunchManager().addLaunchListener(this);
DebugPlugin.getDefault().addDebugEventListener(this);
ResourcesPlugin.getWorkspace().addResourceChangeListener(this,
IResourceChangeEvent.POST_BUILD);
}
public void shutdown() {
ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
DebugPlugin.getDefault().removeDebugEventListener(this);
DebugPlugin.getDefault().getLaunchManager().removeLaunchListener(this);
}
/**
* 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(IHotCodeReplaceListener 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(IHotCodeReplaceListener listener) {
fHotCodeReplaceListeners.remove(listener);
}
/**
* Notifies listeners that a hot code replace attempt succeeded
*/
private void fireHCRSucceeded(IScriptDebugTarget target) {
for (IHotCodeReplaceListener listener : fHotCodeReplaceListeners) {
listener.hotCodeReplaceSucceeded(target);
}
}
/**
* Notifies listeners that a hot code replace attempt failed with the given
* exception
*/
private void fireHCRFailed(IScriptDebugTarget target,
DebugException exception) {
for (IHotCodeReplaceListener listener : fHotCodeReplaceListeners) {
listener.hotCodeReplaceFailed(target, exception);
}
}
@Override
public void launchAdded(ILaunch launch) {
IDebugTarget[] debugTargets = launch.getDebugTargets();
for (int i = 0; i < debugTargets.length; i++) {
IScriptDebugTarget target = debugTargets[i]
.getAdapter(IScriptDebugTarget.class);
if (target != null) {
if (supportsHotCodeReplace(target)) {
addHotSwapTarget(target);
} else {
addNonHotSwapTarget(target);
}
}
}
}
public boolean supportsHotCodeReplace(IScriptDebugTarget target) {
final IDLTKLanguageToolkit toolkit = target.getLanguageToolkit();
return toolkit != null
&& getHotCodeReplaceProvider(toolkit.getNatureId()) != null;
}
@Override
public void launchChanged(ILaunch launch) {
launchAdded(launch);
}
@Override
public void launchRemoved(ILaunch launch) {
IDebugTarget[] debugTargets = launch.getDebugTargets();
for (int i = 0; i < debugTargets.length; i++) {
IScriptDebugTarget target = debugTargets[i]
.getAdapter(IScriptDebugTarget.class);
if (target != null) {
deregisterTarget(target);
}
}
}
@Override
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
&& source instanceof IDebugTarget) {
IScriptDebugTarget target = ((IAdaptable) source)
.getAdapter(IScriptDebugTarget.class);
if (target != null) {
deregisterTarget(target);
}
}
}
}
}
private void addHotSwapTarget(IScriptDebugTarget target) {
if (!fHotSwapTargets.contains(target)) {
fHotSwapTargets.add(target);
}
}
private void addNonHotSwapTarget(IScriptDebugTarget target) {
if (!fNoHotSwapTargets.contains(target)) {
fNoHotSwapTargets.add(target);
}
}
private void deregisterTarget(IScriptDebugTarget target) {
if (!fHotSwapTargets.remove(target)) {
fNoHotSwapTargets.remove(target);
}
}
/**
* Returns the currently registered debug targets that support hot code
* replace.
*/
private IScriptDebugTarget[] getHotSwapTargets() {
return (IScriptDebugTarget[]) fHotSwapTargets
.toArray(new IScriptDebugTarget[fHotSwapTargets.size()]);
}
/**
* Returns the currently registered debug targets that do not support hot
* code replace.
*/
private IScriptDebugTarget[] getNoHotSwapTargets() {
return (IScriptDebugTarget[]) fNoHotSwapTargets
.toArray(new IScriptDebugTarget[fNoHotSwapTargets.size()]);
}
@Override
public void resourceChanged(IResourceChangeEvent event) {
if (fHotSwapTargets.isEmpty() && fNoHotSwapTargets.isEmpty()) {
// If there are no targets to notify, only update the build times.
return;
}
IResource[] resources = getChangedFiles(event);
if (resources.length != 0) {
notifyTargets(resources);
}
}
private void notifyTargets(final IResource[] resources) {
final IScriptDebugTarget[] hotSwapTargets = getHotSwapTargets();
final IScriptDebugTarget[] noHotSwapTargets = getNoHotSwapTargets();
if (hotSwapTargets.length != 0) {
Runnable runnable = () -> doHotCodeReplace(hotSwapTargets,
resources);
DebugPlugin.getDefault().asyncExec(runnable);
}
if (noHotSwapTargets.length != 0) {
Runnable runnable = () -> notifyUnsupportedHCR(noHotSwapTargets,
resources);
DebugPlugin.getDefault().asyncExec(runnable);
}
}
protected void notifyUnsupportedHCR(IScriptDebugTarget[] noHotSwapTargets,
IResource[] resources) {
for (int i = 0; i < noHotSwapTargets.length; i++) {
IScriptDebugTarget target = noHotSwapTargets[i];
fireHCRFailed(target, null);
}
}
protected void doHotCodeReplace(IScriptDebugTarget[] hotSwapTargets,
IResource[] resources) {
for (int i = 0; i < hotSwapTargets.length; i++) {
IScriptDebugTarget target = hotSwapTargets[i];
String natureId = target.getLanguageToolkit().getNatureId();
IHotCodeReplaceProvider provider = getHotCodeReplaceProvider(
natureId);
try {
if (provider != null) {
provider.performCodeReplace(target, resources);
fireHCRSucceeded(target);
} else {
fail(NLS.bind(
Messages.HotCodeReplaceManager_hotCodeReplaceProviderForNotFound,
natureId));
}
} catch (DebugException e) {
fireHCRFailed(target, e);
}
}
}
private void fail(String message) throws DebugException {
fail(message, null);
}
private void fail(String message, Throwable e) throws DebugException {
throw new DebugException(
new Status(IStatus.ERROR, DLTKDebugPlugin.PLUGIN_ID,
DebugPlugin.INTERNAL_ERROR, message, e));
}
private IHotCodeReplaceProvider getHotCodeReplaceProvider(String natureId) {
return (IHotCodeReplaceProvider) providerManager.getObject(natureId);
}
/**
* Returns the changed class files. Returns <code>null</code> if the visitor
* encounters an exception, or the delta is not a POST_BUILD.
*/
private IResource[] getChangedFiles(IResourceChangeEvent event) {
IResourceDelta delta = event.getDelta();
if (event.getType() != IResourceChangeEvent.POST_BUILD
|| delta == null) {
return null;
}
ChangedFilesVisitor changedFilesVisitor = new ChangedFilesVisitor();
try {
delta.accept(changedFilesVisitor);
} catch (CoreException e) {
DLTKDebugPlugin.log(e);
return new IResource[0]; // quiet failure
}
return changedFilesVisitor.getChangedFiles();
}
/**
* A visitor which collects changed class files.
*/
class ChangedFilesVisitor implements IResourceDeltaVisitor {
/**
* The collection of changed class files.
*/
protected ArrayList fFiles = new ArrayList();
/**
* Answers whether children should be visited.
* <p>
* If the associated resource is a 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;
fFiles.add(resource);
return false;
default:
return true;
}
}
return true;
}
/**
* Answers a collection of changed files
*/
public IResource[] getChangedFiles() {
return (IResource[]) fFiles.toArray(new IResource[fFiles.size()]);
}
}
}