| 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()]); |
| } |
| } |
| } |