| /*=============================================================================# |
| # Copyright (c) 2005, 2019 Stephan Wahlbrink and others. |
| # |
| # This program and the accompanying materials are made available under the |
| # terms of the Eclipse Public License 2.0 which is available at |
| # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 |
| # which is available at https://www.apache.org/licenses/LICENSE-2.0. |
| # |
| # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 |
| # |
| # Contributors: |
| # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation |
| #=============================================================================*/ |
| |
| package org.eclipse.statet.nico.core.runtime; |
| |
| import static org.eclipse.statet.nico.core.runtime.IToolEventHandler.REPORT_STATUS_DATA_KEY; |
| import static org.eclipse.statet.nico.core.runtime.IToolEventHandler.REPORT_STATUS_EVENT_ID; |
| import static org.eclipse.statet.nico.core.runtime.IToolEventHandler.SCHEDULE_QUIT_EVENT_ID; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.core.filesystem.IFileStore; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.debug.core.DebugEvent; |
| import org.eclipse.debug.core.model.IThread; |
| import org.eclipse.osgi.util.NLS; |
| |
| import org.eclipse.statet.jcommons.collections.CopyOnWriteIdentityListSet; |
| import org.eclipse.statet.jcommons.collections.CopyOnWriteList; |
| import org.eclipse.statet.jcommons.collections.ImCollections; |
| import org.eclipse.statet.jcommons.collections.ImIdentityList; |
| import org.eclipse.statet.jcommons.collections.ImList; |
| import org.eclipse.statet.jcommons.lang.Disposable; |
| import org.eclipse.statet.jcommons.lang.ObjectUtils; |
| import org.eclipse.statet.jcommons.lang.ObjectUtils.ToStringBuilder; |
| import org.eclipse.statet.jcommons.status.CancelStatus; |
| import org.eclipse.statet.jcommons.status.ErrorStatus; |
| import org.eclipse.statet.jcommons.status.InfoStatus; |
| import org.eclipse.statet.jcommons.status.NullProgressMonitor; |
| import org.eclipse.statet.jcommons.status.ProgressMonitor; |
| import org.eclipse.statet.jcommons.status.Status; |
| import org.eclipse.statet.jcommons.status.StatusException; |
| import org.eclipse.statet.jcommons.ts.core.SystemRunnable; |
| import org.eclipse.statet.jcommons.ts.core.Tool; |
| import org.eclipse.statet.jcommons.ts.core.ToolCommandHandler; |
| import org.eclipse.statet.jcommons.ts.core.ToolRunnable; |
| import org.eclipse.statet.jcommons.ts.core.ToolService; |
| |
| import org.eclipse.statet.ecommons.runtime.core.util.StatusUtils; |
| |
| import org.eclipse.statet.internal.nico.core.Messages; |
| import org.eclipse.statet.internal.nico.core.NicoCorePlugin; |
| import org.eclipse.statet.internal.nico.core.RunnableProgressData; |
| import org.eclipse.statet.nico.core.NicoCore; |
| import org.eclipse.statet.nico.core.NicoCoreMessages; |
| import org.eclipse.statet.nico.core.runtime.Queue.Section; |
| |
| |
| /** |
| * Controller for a long running tight integrated tool. |
| * <p> |
| * Usage: This class is intend to be subclass. Subclasses are responsible for the |
| * life cycle of the tool (<code>startTool()</code>, <code>terminateTool()</code>. |
| * Subclasses should provide an interface which can be used by IToolRunnables |
| * to access the features of the tool. E.g. provide an abstract implementation of |
| * IToolRunnable with the necessary methods (in protected scope).</p> |
| * |
| * Methods with protected visibility and marked with an L at the end their names must be called only |
| * within the lifecycle thread. |
| */ |
| public abstract class ToolController implements ConsoleService { |
| |
| |
| private static final boolean DEBUG_LOG_STATE= Boolean.parseBoolean( |
| Platform.getDebugOption("org.eclipse.statet.nico/debug/ToolController/logState") ); //$NON-NLS-1$ |
| |
| private static final int STEP_BEGIN= (DebugEvent.STEP_INTO | DebugEvent.STEP_OVER | DebugEvent.STEP_RETURN); |
| |
| |
| /** |
| * Listens for changes of the status of a controller. |
| * |
| * Use this only if it's really necessary, otherwise listen to |
| * debug events of the ToolProcess. |
| */ |
| public static interface IToolStatusListener { |
| |
| /** |
| * Should be fast! |
| * |
| * This method is called in the tool lifecycle thread |
| * and blocks the queue. |
| * |
| * @param oldStatus |
| * @param newStatus |
| * @param eventCollection a collection, you can add you own debug events to. |
| */ |
| void controllerStatusChanged(ToolStatus oldStatus, ToolStatus newStatus, List<DebugEvent> eventCollection); |
| |
| // void controllerBusyChanged(boolean isBusy, final List<DebugEvent> eventCollection); |
| |
| } |
| |
| |
| private static IProgressMonitor fgProgressMonitorDummy= new org.eclipse.core.runtime.NullProgressMonitor(); |
| |
| |
| protected abstract class ControllerSystemRunnable implements SystemRunnable { |
| |
| |
| private final String fTypeId; |
| private final String fLabel; |
| |
| |
| public ControllerSystemRunnable(final String typeId, final String label) { |
| this.fTypeId= typeId; |
| this.fLabel= label; |
| } |
| |
| |
| @Override |
| public String getTypeId() { |
| return this.fTypeId; |
| } |
| |
| @Override |
| public String getLabel() { |
| return this.fLabel; |
| } |
| |
| @Override |
| public boolean canRunIn(final Tool tool) { |
| return (tool == getTool()); |
| } |
| |
| @Override |
| public boolean changed(final int event, final Tool tool) { |
| if (event == MOVING_FROM) { |
| return false; |
| } |
| return true; |
| } |
| |
| } |
| |
| /** |
| * Default implementation of a runnable which can be used for |
| * {@link ToolController#createCommandRunnable(String, SubmitType)}. |
| * |
| * Usage: This class is intend to be subclassed. |
| */ |
| public abstract static class ConsoleCommandRunnable implements ConsoleRunnable { |
| |
| public static final String TYPE_ID= "common/console/input"; //$NON-NLS-1$ |
| |
| protected final String fText; |
| protected String fLabel; |
| protected final SubmitType fType; |
| |
| protected ConsoleCommandRunnable(final String text, final SubmitType type) { |
| assert (text != null); |
| assert (type != null); |
| |
| this.fText= text; |
| this.fType= type; |
| } |
| |
| @Override |
| public String getTypeId() { |
| return TYPE_ID; |
| } |
| |
| @Override |
| public SubmitType getSubmitType() { |
| return this.fType; |
| } |
| |
| public String getCommand() { |
| return this.fText; |
| } |
| |
| @Override |
| public String getLabel() { |
| if (this.fLabel == null) { |
| this.fLabel= this.fText.trim(); |
| } |
| return this.fLabel; |
| } |
| |
| @Override |
| public boolean changed(final int event, final Tool process) { |
| return true; |
| } |
| |
| @Override |
| public void run(final ToolService service, |
| final ProgressMonitor m) throws StatusException { |
| ((ConsoleService) service).submitToConsole(this.fText, m); |
| } |
| |
| } |
| |
| protected class StartRunnable implements ConsoleRunnable { |
| |
| public StartRunnable() { |
| } |
| |
| @Override |
| public String getTypeId() { |
| return START_TYPE_ID; |
| } |
| |
| @Override |
| public boolean canRunIn(final Tool tool) { |
| return (tool == getTool()); |
| } |
| |
| @Override |
| public SubmitType getSubmitType() { |
| return SubmitType.CONSOLE; |
| } |
| |
| @Override |
| public String getLabel() { |
| return Messages.ToolController_CommonStartTask_label; |
| } |
| |
| @Override |
| public boolean changed(final int event, final Tool process) { |
| if ((event & MASK_EVENT_GROUP) == REMOVING_EVENT_GROUP) { |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public void run(final ToolService s, |
| final ProgressMonitor m) throws StatusException { |
| } |
| |
| } |
| |
| private class SuspendedInsertRunnable extends ControllerSystemRunnable implements SystemRunnable { |
| |
| private final int level; |
| |
| public SuspendedInsertRunnable(final int level) { |
| super(SUSPENDED_INSERT_TYPE_ID, "Suspended [" + level + "]"); |
| this.level= level; |
| } |
| |
| @Override |
| public boolean changed(final int event, final Tool process) { |
| switch (event) { |
| case REMOVING_FROM: |
| case MOVING_FROM: |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public void run(final ToolService service, |
| final ProgressMonitor m) throws StatusException { |
| } |
| |
| } |
| |
| private class SuspendedUpdateRunnable extends ControllerSystemRunnable implements SystemRunnable { |
| |
| public SuspendedUpdateRunnable() { |
| super(SUSPENDED_INSERT_TYPE_ID, "Update Debug Context"); |
| } |
| |
| |
| @Override |
| public boolean changed(final int event, final Tool tool) { |
| if (event == MOVING_FROM) { |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public void run(final ToolService service, |
| final ProgressMonitor m) throws StatusException { |
| final ImList<SystemRunnable> runnables= ToolController.this.suspendUpdateRunnables.toList(); |
| for (final SystemRunnable runnable : runnables) { |
| try { |
| runnable.run(service, m); |
| } |
| catch (final StatusException e) { |
| final Status status= e.getStatus(); |
| if (status != null && (status.getSeverity() == Status.CANCEL || status.getSeverity() <= Status.INFO)) { |
| // ignore |
| } |
| else { |
| NicoCorePlugin.logError(-1, NLS.bind( |
| "An error occurred when running suspend task ''{0}''.", //$NON-NLS-1$ |
| runnable.getLabel() ), e); |
| } |
| if (isTerminated()) { |
| return; |
| } |
| } |
| } |
| } |
| |
| } |
| |
| protected abstract class SuspendResumeRunnable extends ControllerSystemRunnable implements ConsoleRunnable { |
| |
| private int detail; |
| |
| public SuspendResumeRunnable(final String id, final String label, final int detail) { |
| super(id, label); |
| this.detail= detail; |
| } |
| |
| @Override |
| public SubmitType getSubmitType() { |
| return SubmitType.TOOLS; |
| } |
| |
| protected void setDetail(final int detail) { |
| this.detail= detail; |
| } |
| |
| @Override |
| public boolean changed(final int event, final Tool process) { |
| switch (event) { |
| case MOVING_FROM: |
| return false; |
| case REMOVING_FROM: |
| case BEING_ABANDONED: |
| if (ToolController.this.suspendExitRunnable == this) { |
| ToolController.this.suspendExitRunnable= null; |
| } |
| break; |
| default: |
| break; |
| } |
| return true; |
| } |
| |
| @Override |
| public void run(final ToolService adapter, |
| final ProgressMonitor m) throws StatusException { |
| ToolController.this.suspendExitRunnable= this; |
| setSuspended(ToolController.this.suspendedLowerLevel, 0, null); |
| } |
| |
| protected boolean canExec(final ProgressMonitor m) throws StatusException { |
| return true; |
| } |
| |
| protected abstract void doExec(final ProgressMonitor m) throws StatusException; |
| |
| protected void submitToConsole(final String print, final String send, |
| final ProgressMonitor m) throws StatusException { |
| ToolController.this.currentSubmitType= getSubmitTypeL(this); |
| try { |
| ToolController.this.fCurrentInput= print; |
| doBeforeSubmitL(); |
| } |
| finally { |
| ToolController.this.currentSubmitType= ToolController.this.currentRunnable.submitType; |
| } |
| if (send != null) { |
| ToolController.this.fCurrentInput= send; |
| doSubmitL(m); |
| } |
| } |
| |
| } |
| |
| protected class QuitRunnable extends SuspendResumeRunnable { |
| |
| public QuitRunnable() { |
| super(ToolController.QUIT_TYPE_ID, "Quit", DebugEvent.CLIENT_REQUEST); |
| } |
| |
| |
| @Override |
| public void run(final ToolService service, |
| final ProgressMonitor m) throws StatusException { |
| super.run(service, m); |
| if (!ToolController.this.isTerminated) { |
| try { |
| briefAboutToChange(); |
| ((ToolController) service).doQuitL(m); |
| } |
| catch (final StatusException e) { |
| if (!ToolController.this.isTerminated) { |
| handleStatus(new ErrorStatus(NicoCore.BUNDLE_ID, |
| "An error occured when running quit command.", |
| e ), |
| m ); |
| } |
| } |
| } |
| } |
| |
| @Override |
| protected void doExec(final ProgressMonitor m) throws StatusException { |
| } |
| |
| } |
| |
| |
| protected static class RunnableData { |
| |
| private final ToolRunnable runnable; |
| private final SubmitType submitType; |
| private final Section queueSection; |
| |
| public RunnableData(final ToolRunnable runnable, final SubmitType submitType, final Section queueSection) { |
| this.runnable= runnable; |
| this.submitType= submitType; |
| this.queueSection= queueSection; |
| } |
| |
| } |
| |
| |
| public static final String START_TYPE_ID= "common/start"; //$NON-NLS-1$ |
| public static final String QUIT_TYPE_ID= "common/quit"; //$NON-NLS-1$ |
| |
| public static final String SUSPENDED_INSERT_TYPE_ID= "common/debug/suspended.insert"; //$NON-NLS-1$ |
| public static final String SUSPENDED_UPDATE_TYPE_ID= "common/debug/suspended.update"; //$NON-NLS-1$ |
| public static final String SUSPEND_TYPE_ID= "common/debug/suspend"; //$NON-NLS-1$ |
| public static final String RESUME_TYPE_ID= "common/debug/resume"; //$NON-NLS-1$ |
| public static final String STEP_INTO_TYPE_ID= "common/debug/step.in"; //$NON-NLS-1$ |
| public static final String STEP_OVER_TYPE_ID= "common/debug/step.over"; //$NON-NLS-1$ |
| public static final String STEP_RETURN_TYPE_ID= "common/debug/step.return"; //$NON-NLS-1$ |
| |
| public static final int CANCEL_CURRENT= 0x00; |
| public static final int CANCEL_ALL= 0x01; |
| public static final int CANCEL_PAUSE= 0x10; |
| |
| protected static final int SUSPENDED_TOPLEVEL= 0x1; |
| protected static final int SUSPENDED_DEEPLEVEL= 0x2; |
| |
| private static final byte REGULAR= 0; |
| private static final byte HOT_REGULAR= 1; |
| private static final byte HOT_NESTED= 2; |
| |
| |
| private ToolStreamProxy streams; |
| |
| private final ToolProcess process; |
| private final Queue queue; |
| |
| private int counter= 0; |
| |
| private RunnableData currentRunnable; |
| private SubmitType currentSubmitType; |
| private final List<SystemRunnable> controllerRunnables= new ArrayList<>(); |
| private SystemRunnable postControllerRunnable; |
| private RunnableProgressData runnableProgressData; |
| |
| private Thread controllerThread; |
| private ToolStatus status= ToolStatus.STARTING; |
| private ToolStatus statusPrevious; |
| private final CopyOnWriteIdentityListSet<IToolStatusListener> toolStatusListeners= new CopyOnWriteIdentityListSet<>(); |
| private int internalTask; |
| private boolean terminateForced; |
| private volatile boolean isTerminated; |
| |
| private boolean hotModeDeferred; |
| private boolean hotModeNested= true; |
| private byte hotMode= REGULAR; |
| private final ProgressMonitor hotModeMonitor= new NullProgressMonitor(); |
| |
| private boolean isDebugEnabled; |
| |
| private int suspendedRequestLevel; |
| private int loopCurrentLevel; // only within loop |
| private int suspendedRunLevel; // also when running exit/continue suspended |
| private int suspendedLowerLevel; |
| private final CopyOnWriteList<SystemRunnable> suspendUpdateRunnables= new CopyOnWriteList<>(); |
| private SuspendResumeRunnable suspendExitRunnable; |
| private int suspendEnterDetail; |
| private Object suspendEnterData; |
| private int suspendExitDetail; |
| |
| private ToolWorkspace workspaceData; |
| |
| private volatile int currentStamp; |
| private int changeStamp; |
| private int hotStamp; |
| |
| private final Map<String, ToolCommandHandler> actionHandlers= new HashMap<>(); |
| |
| // RunnableAdapter proxy for tool lifecycle thread |
| protected String fCurrentInput; |
| protected Prompt fCurrentPrompt; |
| protected Prompt fDefaultPrompt; |
| protected String fLineSeparator; |
| |
| private final CopyOnWriteIdentityListSet<Disposable> disposables= new CopyOnWriteIdentityListSet<>(); |
| |
| |
| protected ToolController(final ToolProcess process, final Map<String, Object> connectionInfo) { |
| this.process= process; |
| this.process.connectionInfo= connectionInfo; |
| |
| this.streams= new ToolStreamProxy(); |
| |
| this.queue= process.getQueue(); |
| this.toolStatusListeners.add(this.process); |
| |
| this.status= ToolStatus.STARTING; |
| this.runnableProgressData= new RunnableProgressData(Messages.Progress_Starting_label); |
| this.fCurrentPrompt= Prompt.NONE; |
| } |
| |
| |
| protected void setWorksapceData(final ToolWorkspace workspaceData) { |
| this.workspaceData= workspaceData; |
| } |
| |
| |
| public final void addCommandHandler(final String commandId, final ToolCommandHandler handler) { |
| this.actionHandlers.put(commandId, handler); |
| } |
| |
| public final ToolCommandHandler getCommandHandler(final String commandId) { |
| return this.actionHandlers.get(commandId); |
| } |
| |
| /** |
| * Adds a tool status listener. |
| * |
| * @param listener |
| */ |
| public final void addToolStatusListener(final IToolStatusListener listener) { |
| this.toolStatusListeners.add(listener); |
| } |
| |
| /** |
| * Removes the tool status listener. |
| * |
| * @param listener |
| */ |
| public final void removeToolStatusListener(final IToolStatusListener listener) { |
| this.toolStatusListeners.remove(listener); |
| } |
| |
| |
| protected void setStartupTimestamp(final long timestamp) { |
| this.process.setStartupTimestamp(timestamp); |
| } |
| |
| protected void setStartupWD(final String wd) { |
| this.process.setStartupWD(wd); |
| } |
| |
| protected void addDisposable(final Disposable disposable) { |
| this.disposables.add(disposable); |
| } |
| |
| protected final Queue getQueue() { |
| return this.queue; |
| } |
| |
| public final ToolStatus getStatus() { |
| synchronized (this.queue) { |
| return this.status; |
| } |
| } |
| |
| protected final ToolStatus getStatusL() { |
| return this.status; |
| } |
| |
| public final IProgressInfo getProgressInfo() { |
| return this.runnableProgressData; |
| } |
| |
| protected final Thread getControllerThread() { |
| return this.controllerThread; |
| } |
| |
| |
| public final ToolStreamProxy getStreams() { |
| return this.streams; |
| } |
| |
| @Override |
| public ToolProcess getTool() { |
| return this.process; |
| } |
| |
| |
| /** |
| * Runs the tool. |
| * |
| * This method should be called only in a thread explicit for this tool process. |
| * The thread exits this method, if the tool is terminated. |
| */ |
| public final void run() throws StatusException { |
| assert (this.status == ToolStatus.STARTING); |
| try { |
| final Section queueSection= this.queue.getTopLevelSection(); |
| |
| this.controllerThread= Thread.currentThread(); |
| setCurrentRunnable(createRunnableData(createStartRunnable(), queueSection)); |
| startToolL(this.runnableProgressData.getRoot()); |
| setCurrentRunnable(new RunnableData(null, SubmitType.CONSOLE, queueSection)); |
| synchronized (this.queue) { |
| loopChangeStatus((this.controllerRunnables.isEmpty()) ? |
| ToolStatus.STARTED_IDLING : ToolStatus.STARTED_PROCESSING, null); |
| } |
| loopTopLevel(queueSection); |
| } |
| finally { |
| synchronized (this.queue) { |
| if (!this.isTerminated) { |
| this.isTerminated= true; |
| } |
| loopChangeStatus(ToolStatus.TERMINATED, null); |
| this.queue.notifyAll(); |
| } |
| clear(); |
| this.controllerThread= null; |
| } |
| } |
| |
| public final int getHotTasksState() { |
| synchronized (this.queue) { |
| return this.hotMode; |
| } |
| } |
| |
| protected final boolean isInHotModeL() { |
| return (this.hotMode != 0); |
| } |
| |
| /** |
| * Returns whether the tool is suspended. |
| * It is suspended if the tool status is {@link ToolStatus#STARTED_SUSPENDED suspended}, |
| * but also if it is processing or paused within the suspended mode of the tool (e.g. |
| * evaluations to inspect the objects). |
| * |
| * @return <code>true</code> if suspended, otherwise <code>false</code> |
| */ |
| public final boolean isSuspended() { |
| synchronized (this.queue) { |
| return (this.suspendedRequestLevel > 0 || this.loopCurrentLevel > 0); |
| } |
| } |
| |
| /** |
| * {@link #isSuspended()} |
| */ |
| protected final boolean isSuspendedL() { |
| return (this.suspendedRequestLevel > 0 || this.loopCurrentLevel > 0); |
| } |
| |
| /** |
| * Returns the current value of the task counter. The counter is increas |
| * before running a task. |
| * |
| * @return the counter value |
| */ |
| public int getTaskCounter() { |
| return this.counter; |
| } |
| |
| /** |
| * Tries to apply "cancel". |
| * |
| * The return value signals if the command was applicable and/or |
| * was succesful (depends on implementation). |
| * |
| * @return hint about success. |
| */ |
| public final boolean cancelTask(final int options) { |
| synchronized (this.queue) { |
| if ((options & CANCEL_ALL) != 0) { |
| final List<ToolRunnable> list= this.queue.internal_getCurrentList(); |
| list.clear(); |
| } |
| if ((options & CANCEL_PAUSE) != 0) { |
| this.queue.pause(); |
| } |
| this.runnableProgressData.setCanceled(true); |
| beginInternalTask(); |
| |
| if (this.suspendedRequestLevel > this.loopCurrentLevel) { |
| setSuspended(this.loopCurrentLevel, 0, null); |
| this.suspendExitDetail= DebugEvent.RESUME; |
| } |
| else if (this.loopCurrentLevel > this.suspendedLowerLevel) { |
| setSuspended(this.suspendedLowerLevel, 0, null); |
| this.suspendExitDetail= DebugEvent.RESUME; |
| } |
| } |
| |
| try { |
| interruptTool(); |
| return true; |
| } |
| catch (final UnsupportedOperationException e) { |
| return false; |
| } |
| finally { |
| synchronized (this.queue) { |
| scheduleControllerRunnable(createCancelPostRunnable(options)); |
| endInternalTask(); |
| } |
| } |
| } |
| |
| /** |
| * Requests to terminate the controller/process asynchronously. |
| * |
| * Same as ToolProcess.terminate() |
| * The tool will shutdown (after the next runnable) usually. |
| */ |
| public final void scheduleQuit() { |
| synchronized (this.queue) { |
| if (this.status == ToolStatus.TERMINATED) { |
| return; |
| } |
| beginInternalTask(); |
| } |
| |
| // ask handler |
| boolean schedule= true; |
| try { |
| final ToolCommandHandler handler= this.actionHandlers.get(SCHEDULE_QUIT_EVENT_ID); |
| if (handler != null) { |
| final Map<String, Object> data= new HashMap<>(); |
| data.put("scheduledQuitRunnables", getQuitRunnables()); //$NON-NLS-1$ |
| final Status status= executeHandler(SCHEDULE_QUIT_EVENT_ID, handler, data, new NullProgressMonitor()); |
| if (status != null && status.getSeverity() > Status.OK) { |
| schedule= false; |
| } |
| } |
| } |
| finally { |
| synchronized (this.queue) { |
| if (this.status != ToolStatus.TERMINATED) { |
| if (schedule) { |
| final ToolRunnable runnable= createQuitRunnable(); |
| this.queue.add(runnable); |
| } |
| } |
| endInternalTask(); |
| } |
| } |
| } |
| |
| protected void setTracks(final List<? extends ITrack> tracks) { |
| this.process.setTracks(tracks); |
| } |
| |
| protected final void beginInternalTask() { |
| this.internalTask++; |
| } |
| |
| protected final void endInternalTask() { |
| this.internalTask--; |
| if (this.controllerRunnables.size() > 0 || this.internalTask == 0) { |
| this.queue.notifyAll(); |
| } |
| } |
| |
| protected abstract ToolRunnable createStartRunnable(); |
| |
| /** |
| * Creates a runnable to which can quit the tool. |
| * The type should be QUIT_TYPE_ID. |
| * @return |
| */ |
| protected QuitRunnable createQuitRunnable() { |
| return new QuitRunnable(); |
| } |
| |
| protected SystemRunnable createCancelPostRunnable(final int options) { |
| return null; |
| } |
| |
| /** |
| * Cancels requests to terminate the controller. |
| */ |
| public final void cancelQuit() { |
| synchronized(this.queue) { |
| this.queue.remove(getQuitRunnables()); |
| |
| if (this.status == ToolStatus.TERMINATED) { |
| return; |
| } |
| } |
| |
| // cancel task should not be synch |
| final ToolRunnable current= this.currentRunnable.runnable; |
| if (current != null && current.getTypeId() == QUIT_TYPE_ID) { |
| cancelTask(0); |
| } |
| } |
| |
| private final List<ToolRunnable> getQuitRunnables() { |
| final ToolRunnable current; |
| final List<ToolRunnable> waiting; |
| synchronized (this.queue) { |
| current= this.currentRunnable.runnable; |
| waiting= this.queue.internal_getCurrentList(); |
| } |
| final List<ToolRunnable> quitRunnables= new ArrayList<>(); |
| if (current != null && current.getTypeId() == QUIT_TYPE_ID) { |
| quitRunnables.add(current); |
| } |
| for (final ToolRunnable runnable : waiting) { |
| if (runnable.getTypeId() == QUIT_TYPE_ID) { |
| quitRunnables.add(runnable); |
| } |
| } |
| return quitRunnables; |
| } |
| |
| public final void kill(final ProgressMonitor m) throws StatusException { |
| final Thread thread= getControllerThread(); |
| killTool(m); |
| if (thread != null) { |
| for (int i= 0; i < 3; i++) { |
| if (isTerminated()) { |
| return; |
| } |
| synchronized (this.queue) { |
| this.queue.notifyAll(); |
| try { |
| this.queue.wait(10); |
| } |
| catch (final Exception e) {} |
| } |
| thread.interrupt(); |
| } |
| } |
| if (!isTerminated()) { |
| markAsTerminated(); |
| } |
| } |
| |
| /** |
| * Should be only called inside synchronized(fQueue) blocks. |
| * |
| * @param newStatus |
| */ |
| private final void loopChangeStatus(final ToolStatus newStatus, RunnableProgressData newMonitor) { |
| if (this.status != newStatus && newMonitor == null) { |
| newMonitor= new RunnableProgressData(newStatus.getMarkedLabel()); |
| } |
| |
| // update progress info |
| if (newMonitor != null) { |
| this.runnableProgressData= newMonitor; |
| } |
| |
| if (newStatus == this.status) { |
| if (newStatus == ToolStatus.STARTED_PROCESSING && this.loopCurrentLevel > 0) { |
| // for changed resume detail |
| final ImIdentityList<IToolStatusListener> listeners= this.toolStatusListeners.toList(); |
| final List<DebugEvent> eventList= this.queue.internal_getEventList(); |
| for (final IToolStatusListener listener : listeners) { |
| if (listener instanceof IThread) { |
| listener.controllerStatusChanged(this.statusPrevious, newStatus, eventList); |
| } |
| } |
| } |
| else { |
| return; |
| } |
| } |
| else { |
| if (newStatus == ToolStatus.STARTED_SUSPENDED) { |
| this.suspendExitDetail= DebugEvent.UNSPECIFIED; |
| } |
| if (newStatus == ToolStatus.STARTED_PROCESSING |
| && (this.status != ToolStatus.STARTED_PAUSED || this.statusPrevious != ToolStatus.STARTED_PROCESSING)) { |
| this.queue.internal_resetOnIdle(); |
| } |
| |
| this.queue.internal_onStatusChanged(newStatus); |
| |
| this.statusPrevious= this.status; |
| this.status= newStatus; |
| |
| final ImIdentityList<IToolStatusListener> listeners= this.toolStatusListeners.toList(); |
| final List<DebugEvent> eventList= this.queue.internal_getEventList(); |
| for (final IToolStatusListener listener : listeners) { |
| listener.controllerStatusChanged(this.statusPrevious, newStatus, eventList); |
| } |
| } |
| |
| if (DEBUG_LOG_STATE) { |
| logEvents("loopChangeStatus", newStatus); //$NON-NLS-1$ |
| } |
| |
| this.queue.internal_fireEvents(); |
| } |
| |
| // protected final void loopBusyChanged(final boolean isBusy) { |
| // final IToolStatusListener[] listeners= getToolStatusListeners(); |
| // for (int i= 0; i < listeners.length; i++) { |
| // listeners[i].controllerBusyChanged(isBusy, fEventCollector); |
| // } |
| // final DebugPlugin manager= DebugPlugin.getDefault(); |
| // if (manager != null && !fEventCollector.isEmpty()) { |
| // manager.fireDebugEventSet(fEventCollector.toArray(new DebugEvent[fEventCollector.size()])); |
| // } |
| // fEventCollector.clear(); |
| // } |
| |
| // public final boolean isStarted() { |
| // switch (fStatus) { |
| // case PROCESSING_STATE: |
| // case IDLING_STATE: |
| // case PAUSED_STATE: |
| // return true; |
| // default: |
| // return false; |
| // } |
| // } |
| // |
| // public final boolean isTerminated() { |
| // return (fStatus == ToolStatus.TERMINATED); |
| // } |
| |
| /** |
| * Only for internal short tasks. |
| */ |
| protected final void scheduleControllerRunnable(final SystemRunnable runnable) { |
| synchronized (this.queue) { |
| if (!this.controllerRunnables.contains(runnable)) { |
| this.controllerRunnables.add(runnable); |
| } |
| if (this.status != ToolStatus.STARTED_PROCESSING) { |
| this.queue.notifyAll(); |
| } |
| } |
| } |
| |
| protected final void addPostControllerRunnable(final SystemRunnable runnable) { |
| this.postControllerRunnable= runnable; |
| } |
| |
| protected final void removePostControllerRunnable(final ToolRunnable runnable) { |
| if (this.postControllerRunnable == runnable) { |
| this.postControllerRunnable= null; |
| } |
| } |
| |
| /** |
| * Version for one single text line. |
| * @see #submit(List, SubmitType) |
| * |
| * @param text a single text line. |
| * @param type type of this submittal. |
| * @return <code>true</code>, if adding commands to queue was successful, |
| * otherwise <code>false</code>. |
| */ |
| public final Status submit(final String text, final SubmitType type) { |
| return submit(Collections.singletonList(text), type); |
| } |
| |
| /** |
| * Submits one or multiple text lines to the tool. |
| * The texts will be treated as usual commands with console output. |
| * |
| * @param text array with text lines. |
| * @param type type of this submittal. |
| * @param monitor a monitor for cancel, will not be changed. |
| * @return <code>true</code>, if adding commands to queue was successful, |
| * otherwise <code>false</code>. |
| */ |
| public final Status submit(final List<String> text, final SubmitType type, |
| final IProgressMonitor monitor) { |
| try { |
| monitor.beginTask(NicoCoreMessages.SubmitTask_label, 2); |
| assert (text != null); |
| |
| final ToolRunnable[] runs= new ToolRunnable[text.size()]; |
| for (int i= 0; i < text.size(); i++) { |
| runs[i]= createCommandRunnable(text.get(i), type); |
| } |
| |
| if (monitor.isCanceled()) { |
| return new CancelStatus(NicoCore.BUNDLE_ID, |
| Messages.ToolController_SubmitCancelled_message, null ); |
| } |
| monitor.worked(1); |
| |
| return this.queue.add(ImCollections.newList(runs)); |
| } |
| finally { |
| monitor.done(); |
| } |
| } |
| |
| /** |
| * Submits one or multiple text lines to the tool. |
| * The texts will be treated as usual commands with console output. |
| * |
| * @param text array with text lines. |
| * @param type type of this submittal. |
| * @return <code>true</code>, if adding commands to queue was successful, |
| * otherwise <code>false</code>. |
| */ |
| public final Status submit(final List<String> text, final SubmitType type) { |
| return submit(text, type, fgProgressMonitorDummy); |
| } |
| |
| /** |
| * Implement this method to create a runnable for text commands |
| * (e.g. from console or editor). |
| * |
| * The runnable should commit this commands to the tool |
| * and print command and results to the console. |
| * Default implementations creates a {@link ConsoleCommandRunnable}. |
| * |
| * @param command text command |
| * @param type type of this submission |
| * @return runnable for this command |
| */ |
| public abstract ToolRunnable createCommandRunnable(final String command, final SubmitType type); |
| |
| |
| private final void loopTopLevel(final Section queueSection) { |
| if (this.hotModeDeferred) { |
| this.hotModeDeferred= false; |
| scheduleHotMode(); |
| } |
| boolean enterSuspended= false; |
| |
| while (true) { |
| if (enterSuspended) { |
| enterSuspended= false; |
| runSuspendedLoopL(SUSPENDED_TOPLEVEL); |
| } |
| else { |
| loopRunTask(queueSection); |
| } |
| |
| synchronized (this.queue) { // if interrupted run loop, all states are checked |
| this.queue.internal_check(); |
| |
| if (this.internalTask > 0) { |
| try { |
| this.queue.wait(); |
| } |
| catch (final InterruptedException e) {} |
| continue; |
| } |
| if (this.isTerminated) { |
| this.process.setExitValue(finishToolL()); |
| loopChangeStatus(ToolStatus.TERMINATED, null); |
| return; |
| } |
| if (this.controllerRunnables.size() > 0) { |
| continue; |
| } |
| if (this.suspendedRequestLevel > 0) { |
| enterSuspended= true; |
| continue; |
| } |
| if (this.queue.internal_isPauseRequested()) { |
| loopChangeStatus(ToolStatus.STARTED_PAUSED, null); |
| try { |
| this.queue.wait(); |
| } |
| catch (final InterruptedException e) {} |
| continue; |
| } |
| if (this.queue.internal_next() < 0) { |
| loopChangeStatus(ToolStatus.STARTED_IDLING, null); |
| try { |
| this.queue.wait(); |
| } |
| catch (final InterruptedException e) {} |
| continue; |
| } |
| } |
| } |
| } |
| |
| private final void loopSuspended(final int level, final Section queueSection) { |
| boolean enterSuspended= false; |
| |
| while (true) { |
| if (enterSuspended) { |
| enterSuspended= false; |
| runSuspendedLoopL(SUSPENDED_TOPLEVEL); |
| } |
| else { |
| loopRunTask(queueSection); |
| } |
| |
| synchronized (this.queue) { // if interrupted run loop, all states are checked |
| this.queue.internal_check(); |
| |
| if (this.internalTask > 0) { |
| try { |
| this.queue.wait(); |
| } |
| catch (final InterruptedException e) {} |
| continue; |
| } |
| if (this.isTerminated) { |
| return; |
| } |
| if (this.suspendedRequestLevel < level) { |
| return; |
| } |
| if (this.controllerRunnables.size() > 0) { |
| continue; |
| } |
| if (this.suspendedRequestLevel > level) { |
| enterSuspended= true; |
| continue; |
| } |
| if (this.queue.internal_isPauseRequested()) { |
| loopChangeStatus(ToolStatus.STARTED_PAUSED, null); |
| try { |
| this.queue.wait(); |
| } |
| catch (final InterruptedException e) {} |
| continue; |
| } |
| if (this.queue.internal_next() < 0) { |
| loopChangeStatus(ToolStatus.STARTED_SUSPENDED, null); |
| try { |
| this.queue.wait(); |
| } |
| catch (final InterruptedException e) {} |
| continue; |
| } |
| } |
| } |
| } |
| |
| private final void loopRunTask(final Section queueSection) { |
| while (true) { |
| final int type; |
| final RunnableData savedCurrentRunnable= this.currentRunnable; |
| final ToolRunnable runnable; |
| synchronized (this.queue) { |
| if (this.controllerRunnables.size() > 0) { |
| type= Queue.RUN_CONTROL; |
| runnable= this.controllerRunnables.remove(0); |
| } |
| else if (this.loopCurrentLevel != this.suspendedRequestLevel || this.isTerminated |
| || this.internalTask > 0 || this.queue.internal_isPauseRequested()) { |
| return; |
| } |
| else { |
| type= this.queue.internal_next(); |
| switch (type) { |
| case Queue.RUN_HOT: |
| runnable= null; |
| break; |
| case Queue.RUN_OTHER: |
| case Queue.RUN_DEFAULT: |
| runnable= this.queue.internal_poll(); |
| break; |
| default: |
| return; |
| } |
| } |
| if (type != Queue.RUN_HOT) { |
| if (this.loopCurrentLevel > 0) { |
| if (type != Queue.RUN_CONTROL |
| && (runnable instanceof ConsoleCommandRunnable) |
| && !runConsoleCommandInSuspend(((ConsoleCommandRunnable) runnable).fText) ) { |
| try { |
| this.queue.internal_onFinished(runnable, ToolRunnable.FINISHING_CANCEL); |
| } |
| finally { |
| setCurrentRunnable(savedCurrentRunnable); |
| } |
| return; |
| } |
| if (runnable instanceof ToolController.SuspendResumeRunnable) { |
| this.suspendExitDetail= ((ToolController.SuspendResumeRunnable) runnable).detail; |
| } |
| else { |
| final int detail= getDebugResumeDetailL(runnable); |
| if (this.status == ToolStatus.STARTED_SUSPENDED |
| || getDebugResumeDetailPriority(detail) >= getDebugResumeDetailPriority(this.suspendExitDetail)) { |
| this.suspendExitDetail= detail; |
| } |
| } |
| } |
| setCurrentRunnable(createRunnableData(runnable, queueSection)); |
| loopChangeStatus(ToolStatus.STARTED_PROCESSING, |
| new RunnableProgressData(runnable)); |
| } |
| } |
| |
| ProgressMonitor runnableProgressMonitor; |
| switch (type) { |
| case Queue.RUN_CONTROL: |
| runnableProgressMonitor= this.runnableProgressData.getRoot(); |
| try { |
| runnable.run(this, runnableProgressMonitor); |
| safeRunnableChanged(runnable, ToolRunnable.FINISHING_OK); |
| continue; |
| } |
| catch (final Throwable e) { |
| final Status status= (e instanceof StatusException) ? ((StatusException) e).getStatus() : null; |
| if (status != null && (status.getSeverity() == Status.CANCEL || status.getSeverity() <= Status.INFO)) { |
| safeRunnableChanged(runnable, ToolRunnable.FINISHING_CANCEL); |
| // ignore |
| } |
| else { |
| NicoCorePlugin.logError(-1, NLS.bind( |
| "An Error occurred when running internal controller task ''{0}''.", //$NON-NLS-1$ |
| runnable.getLabel() ), e); |
| safeRunnableChanged(runnable, ToolRunnable.FINISHING_ERROR); |
| } |
| |
| if (!isToolAlive()) { |
| markAsTerminated(); |
| } |
| return; |
| } |
| finally { |
| setCurrentRunnable(savedCurrentRunnable); |
| // runnableProgressMonitor.setWorkRemaining(0); |
| } |
| case Queue.RUN_HOT: |
| try { |
| this.hotModeNested= false; |
| if (!initilizeHotMode()) { |
| if (!isToolAlive()) { |
| markAsTerminated(); |
| } |
| } |
| continue; |
| } |
| finally { |
| this.hotModeNested= true; |
| } |
| case Queue.RUN_OTHER: |
| case Queue.RUN_DEFAULT: |
| runnableProgressMonitor= this.runnableProgressData.getRoot(); |
| try { |
| this.counter++; |
| runnableProgressMonitor.setWorkRemaining(100); |
| runnable.run(this, runnableProgressMonitor.newSubMonitor(99)); |
| runnableProgressMonitor.setWorkRemaining(1); |
| onTaskFinished(this.currentRunnable, ToolRunnable.FINISHING_OK, |
| runnableProgressMonitor ); |
| continue; |
| } |
| catch (final Throwable e) { |
| runnableProgressMonitor.setWorkRemaining(1); |
| Status status= (e instanceof StatusException) ? ((StatusException) e).getStatus() : null; |
| if (status != null && ( |
| status.getSeverity() == Status.CANCEL || status.getSeverity() <= Status.INFO)) { |
| onTaskFinished(this.currentRunnable, ToolRunnable.FINISHING_CANCEL, |
| runnableProgressMonitor ); |
| } |
| else { |
| onTaskFinished(this.currentRunnable, ToolRunnable.FINISHING_ERROR, |
| runnableProgressMonitor ); |
| status= new ErrorStatus(NicoCore.BUNDLE_ID, NicoCorePlugin.EXTERNAL_ERROR, |
| String.format("Failed to complete '%1$s'.", runnable.getLabel()), |
| e ); |
| if (type == Queue.RUN_DEFAULT) { |
| handleStatus(status, runnableProgressMonitor); |
| } |
| else { |
| logRunnableStatus(status, this.currentRunnable); |
| } |
| } |
| |
| if (!isToolAlive()) { |
| markAsTerminated(); |
| } |
| return; |
| } |
| finally { |
| runnableProgressMonitor.setWorkRemaining(0); |
| if (this.postControllerRunnable != null) { |
| synchronized (this.queue) { |
| this.controllerRunnables.remove(this.postControllerRunnable); |
| this.controllerRunnables.add(this.postControllerRunnable); |
| } |
| } |
| setCurrentRunnable(savedCurrentRunnable); |
| } |
| } |
| } |
| } |
| |
| /** Only called for regular tasks */ |
| protected void onTaskFinished(final RunnableData runnableData, final int event, |
| final ProgressMonitor m) { |
| this.queue.internal_onFinished(runnableData.runnable, event); |
| safeRunnableChanged(runnableData.runnable, event); |
| } |
| |
| private void safeRunnableChanged(final ToolRunnable runnable, final int event) { |
| try { |
| runnable.changed(event, this.process); |
| } |
| catch (final Throwable e) { |
| NicoCorePlugin.logError(NicoCorePlugin.EXTERNAL_ERROR, |
| NLS.bind(Messages.ToolRunnable_error_RuntimeError_message, |
| this.process.getLabel(Tool.LONG_LABEL), |
| runnable.getLabel() ), |
| e ); |
| } |
| } |
| |
| protected final void scheduleHotMode() { |
| final ToolStatus status; |
| synchronized (this) { |
| status= this.status; |
| } |
| switch (status) { |
| case TERMINATED: |
| return; |
| case STARTING: |
| this.hotModeDeferred= true; |
| return; |
| default: |
| requestHotMode((Thread.currentThread() != this.controllerThread)); |
| return; |
| } |
| } |
| |
| protected void requestHotMode(final boolean async) { |
| } |
| |
| protected boolean initilizeHotMode() { |
| return true; |
| } |
| |
| private final ToolRunnable pollHotRunnable() { |
| ToolRunnable runnable= null; |
| if (!this.isTerminated) { |
| runnable= this.queue.internal_pollHot(); |
| if (runnable == null && !this.hotModeNested) { |
| try { |
| this.queue.wait(100); |
| } |
| catch (final InterruptedException e) { |
| } |
| if (!this.isTerminated) { |
| runnable= this.queue.internal_pollHot(); |
| } |
| } |
| } |
| return runnable; |
| } |
| |
| protected final void runHotModeLoop() { |
| while (true) { |
| final ToolRunnable runnable; |
| synchronized (this.queue) { |
| runnable= pollHotRunnable(); |
| if (runnable == null) { |
| if (this.hotMode != 0) { |
| onHotModeExit(this.hotModeMonitor); |
| this.hotMode= 0; |
| this.currentSubmitType= this.currentRunnable.submitType; |
| } |
| return; |
| } |
| if (this.hotMode == 0) { |
| this.hotMode= (this.hotModeNested) ? HOT_NESTED : HOT_REGULAR; |
| this.currentSubmitType= SubmitType.OTHER; |
| onHotModeEnter(this.hotModeMonitor); |
| } |
| this.hotModeMonitor.setCanceled(false); |
| } |
| try { |
| runnable.run(this, this.hotModeMonitor); |
| safeRunnableChanged(runnable, ToolRunnable.FINISHING_OK); |
| continue; |
| } |
| catch (final Throwable e) { |
| final Status status= (e instanceof StatusException) ? ((StatusException) e).getStatus() : null; |
| if (status != null && (status.getSeverity() == Status.CANCEL || status.getSeverity() <= Status.INFO)) { |
| safeRunnableChanged(runnable, ToolRunnable.FINISHING_CANCEL); |
| // ignore |
| } |
| else { |
| safeRunnableChanged(runnable, ToolRunnable.FINISHING_ERROR); |
| NicoCorePlugin.logError(-1, "An Error occurred when running hot task.", e); // //$NON-NLS-1$ |
| } |
| |
| if (!isToolAlive()) { |
| markAsTerminated(); |
| } |
| } |
| } |
| } |
| |
| protected void onHotModeEnter(final ProgressMonitor m) { |
| if (this.hotMode > HOT_REGULAR) { |
| enableHotStamp(); |
| } |
| } |
| |
| protected void onHotModeExit(final ProgressMonitor m) { |
| disableHotStamp(); |
| } |
| |
| |
| public final boolean isDebugEnabled() { |
| return this.isDebugEnabled; |
| } |
| |
| protected void setDebugEnabled(final boolean enabled) { |
| this.isDebugEnabled= enabled; |
| } |
| |
| protected int getDebugResumeDetailL(final ToolRunnable runnable) { |
| switch (getSubmitTypeL(runnable)) { |
| case CONSOLE: |
| case EDITOR: |
| return DebugEvent.UNSPECIFIED; |
| case OTHER: |
| return DebugEvent.EVALUATION_IMPLICIT; |
| default: |
| return DebugEvent.EVALUATION; |
| } |
| } |
| |
| private int getDebugResumeDetailPriority(final int detail) { |
| switch (detail) { |
| case DebugEvent.EVALUATION_IMPLICIT: |
| return 1; |
| case DebugEvent.EVALUATION: |
| return 2; |
| case DebugEvent.STEP_INTO: |
| case DebugEvent.STEP_OVER: |
| case DebugEvent.STEP_RETURN: |
| return 3; |
| case DebugEvent.UNSPECIFIED: |
| default: |
| return 4; |
| } |
| } |
| |
| |
| protected int setSuspended(final int level, final int enterDetail, final Object enterData) { |
| this.suspendedRequestLevel= level; |
| if (enterDetail == 0 |
| && (this.suspendExitDetail & STEP_BEGIN) != 0) { |
| this.suspendEnterDetail= DebugEvent.STEP_END; |
| this.suspendEnterData= null; |
| } |
| else { |
| this.suspendEnterDetail= enterDetail; |
| this.suspendEnterData= enterData; |
| } |
| |
| return (level - this.suspendedRunLevel); |
| } |
| |
| public void addSuspendUpdateRunnable(final SystemRunnable runnable) { |
| this.suspendUpdateRunnables.add(runnable); |
| } |
| |
| protected boolean runConsoleCommandInSuspend(final String input) { |
| return true; |
| } |
| |
| protected void scheduleSuspendExitRunnable(final SuspendResumeRunnable runnable) throws StatusException { |
| synchronized (this.queue) { |
| if (this.loopCurrentLevel == 0) { |
| return; |
| } |
| if (this.suspendExitRunnable != null) { |
| this.queue.remove(this.suspendExitRunnable); |
| this.controllerRunnables.remove(this.suspendExitRunnable); |
| } |
| this.suspendExitRunnable= runnable; |
| if (Thread.currentThread() == this.controllerThread) { |
| runnable.run(this, null); |
| } |
| else { |
| scheduleControllerRunnable(runnable); |
| } |
| } |
| } |
| |
| protected void runSuspendedLoopL(final int o) { |
| Queue.Section queueSection= null; |
| SystemRunnable updater= null; |
| |
| final RunnableData savedCurrentRunnable= this.currentRunnable; |
| final RunnableProgressData savedProgressMonitor= this.runnableProgressData; |
| final ToolStatus savedStatus= this.status; |
| |
| final int savedLower= this.suspendedLowerLevel; |
| final int savedLevel= this.suspendedLowerLevel= this.suspendedRunLevel; |
| try { |
| while (true) { |
| final int thisLevel; |
| synchronized (this.queue) { |
| thisLevel= this.suspendedRequestLevel; |
| if (thisLevel <= savedLevel) { |
| setSuspended(this.suspendedRequestLevel, 0, null); |
| return; |
| } |
| if (this.loopCurrentLevel != thisLevel || queueSection == null) { |
| this.loopCurrentLevel= this.suspendedRunLevel= thisLevel; |
| |
| queueSection= this.queue.internal_addInsert( |
| new SuspendedInsertRunnable(thisLevel) ); |
| if (savedLevel == 0 && updater == null) { |
| updater= new SuspendedUpdateRunnable(); |
| this.queue.addOnIdle(updater, 6000); |
| } |
| } |
| this.suspendExitDetail= DebugEvent.UNSPECIFIED; |
| } |
| |
| // run suspended loop |
| doRunSuspendedLoopL(o, thisLevel, queueSection); |
| |
| // resume main runnable |
| final SuspendResumeRunnable runnable; |
| synchronized (this.queue) { |
| this.suspendExitDetail= DebugEvent.UNSPECIFIED; |
| if (this.isTerminated) { |
| setSuspended(0, 0, null); |
| return; |
| } |
| this.loopCurrentLevel= savedLevel; |
| this.suspendEnterDetail= DebugEvent.UNSPECIFIED; |
| |
| this.queue.internal_removeInsert(queueSection); |
| queueSection= null; |
| |
| if (thisLevel <= this.suspendedRequestLevel) { |
| continue; |
| } |
| |
| runnable= this.suspendExitRunnable; |
| if (runnable != null) { |
| this.suspendExitRunnable= null; |
| this.queue.remove(runnable); |
| } |
| } |
| |
| if (runnable != null) { // resume with runnable |
| try { |
| setCurrentRunnable((savedCurrentRunnable != null) ? |
| savedCurrentRunnable : |
| createRunnableData(runnable, this.queue.getCurrentSection()) ); |
| if (runnable.canExec(savedProgressMonitor.getRoot())) { // exec resume |
| synchronized (this.queue) { |
| this.suspendExitDetail= runnable.detail; |
| loopChangeStatus(ToolStatus.STARTED_PROCESSING, savedProgressMonitor); |
| } |
| runnable.doExec(savedProgressMonitor.getRoot()); |
| } |
| else { // cancel resume |
| synchronized (this.queue) { |
| this.suspendedRequestLevel= thisLevel; |
| } |
| } |
| } |
| catch (final Exception e) { |
| NicoCorePlugin.logError("An error occurred when executing debug command.", |
| e ); |
| } |
| } |
| else { // resume without runnable |
| this.suspendExitDetail= DebugEvent.UNSPECIFIED; |
| if (savedCurrentRunnable != null) { |
| synchronized (this.queue) { |
| loopChangeStatus(ToolStatus.STARTED_PROCESSING, savedProgressMonitor); |
| } |
| } |
| } |
| } |
| } |
| finally { |
| this.suspendedLowerLevel= savedLower; |
| this.suspendedRunLevel= savedLevel; |
| setCurrentRunnable(savedCurrentRunnable); |
| |
| synchronized (this.queue) { |
| loopChangeStatus(savedStatus, savedProgressMonitor); |
| |
| // if not exit normally |
| if (this.loopCurrentLevel != savedLevel) { |
| this.loopCurrentLevel= savedLevel; |
| this.suspendEnterDetail= DebugEvent.UNSPECIFIED; |
| } |
| if (updater != null) { |
| this.queue.removeOnIdle(updater); |
| } |
| if (queueSection != null) { |
| this.queue.internal_removeInsert(queueSection); |
| } |
| |
| this.suspendExitRunnable= null; |
| setSuspended(this.suspendedRequestLevel, 0, null); |
| } |
| } |
| } |
| |
| protected void doRunSuspendedLoopL(final int o, final int level, final Section queueSection) { |
| loopSuspended(level, queueSection); |
| } |
| |
| protected int getCurrentLevelL() { |
| return this.loopCurrentLevel; |
| } |
| |
| protected int getRequestedLevelL() { |
| return this.suspendedRequestLevel; |
| } |
| |
| public int getSuspendEnterDetail() { |
| return this.suspendEnterDetail; |
| } |
| |
| public Object getSuspendEnterData() { |
| return this.suspendEnterData; |
| } |
| |
| public int getSuspendExitDetail() { |
| return this.suspendExitDetail; |
| } |
| |
| protected final void markAsTerminated() { |
| if (isToolAlive()) { |
| NicoCorePlugin.logError(NicoCore.STATUSCODE_RUNTIME_ERROR, "Illegal state: tool marked as terminated but still alive.", null); //$NON-NLS-1$ |
| } |
| this.isTerminated= true; |
| } |
| |
| protected final boolean isTerminated() { |
| return this.isTerminated; |
| } |
| |
| |
| /** |
| * Implement here special functionality to start the tool. |
| * |
| * The method is called automatically in the tool lifecycle thread. |
| * |
| * @param m a progress monitor |
| * |
| * @throws StatusException with details, if start fails. |
| */ |
| protected abstract void startToolL(ProgressMonitor m) throws StatusException; |
| |
| /** |
| * Implement here special commands to kill the tool. |
| * |
| * The method is can be called async. |
| * |
| * @param a progress monitor |
| */ |
| protected abstract void killTool(ProgressMonitor m); |
| |
| /** |
| * Checks if the tool is still alive. |
| * |
| * @return <code>true</code> if ok, otherwise <code>false</code>. |
| */ |
| protected abstract boolean isToolAlive(); |
| |
| /** |
| * Interrupts the tool. This methods can be called async. |
| */ |
| protected void interruptTool() throws UnsupportedOperationException { |
| final Thread thread= getControllerThread(); |
| if (thread != null) { |
| thread.interrupt(); |
| } |
| } |
| |
| /** |
| * Is called, after termination is detected. |
| * |
| * @return exit code |
| */ |
| protected int finishToolL() { |
| return 0; |
| } |
| |
| /** |
| * Implement here special commands to deallocate resources. |
| * |
| * Call super! |
| * The method is called automatically in the tool lifecycle thread |
| * after the tool is terminated. |
| */ |
| protected void clear() { |
| this.streams.dispose(); |
| this.streams= null; |
| |
| final ImIdentityList<Disposable> disposables= this.disposables.clearToList(); |
| for (final Disposable disposable : disposables) { |
| try { |
| disposable.dispose(); |
| } |
| catch (final Exception e) { |
| NicoCorePlugin.logError("An unexepected exception is thrown when disposing a controller extension.", |
| e ); |
| } |
| } |
| } |
| |
| @Override |
| public void handleStatus(final Status status, final ProgressMonitor m) { |
| if (status == null || status.getSeverity() == Status.OK) { |
| return; |
| } |
| final ToolCommandHandler handler= getCommandHandler(REPORT_STATUS_EVENT_ID); |
| if (handler != null) { |
| final Map<String, Object> data= Collections.singletonMap(REPORT_STATUS_DATA_KEY, (Object) status); |
| final Status reportStatus= executeHandler(REPORT_STATUS_EVENT_ID, handler, data, m); |
| if (reportStatus != null && reportStatus.getSeverity() == Status.OK) { |
| return; |
| } |
| } |
| if (status.getSeverity() > Status.INFO) { |
| logRunnableStatus(status, this.currentRunnable); |
| } |
| } |
| |
| private void logRunnableStatus(final Status status, final RunnableData runnableData) { |
| NicoCorePlugin.log(new org.eclipse.core.runtime.MultiStatus(NicoCore.BUNDLE_ID, 0, |
| new IStatus[] { StatusUtils.convert(status) }, |
| NicoCoreMessages.format_ProblemWhileRunningTask_message( |
| runnableData.runnable.getLabel(), |
| this.process ), |
| null )); |
| } |
| |
| protected Status executeHandler(final String commandID, final ToolCommandHandler handler, |
| final Map<String, Object> data, final ProgressMonitor m) { |
| try { |
| return handler.execute(commandID, this, data, m); |
| } |
| catch (final Exception e) { |
| NicoCorePlugin.logError(NLS.bind("An error occurred when executing tool command ''{0}''.", |
| commandID ), e ); |
| return null; |
| } |
| } |
| |
| |
| //-- RunnableAdapter - access only in main loop |
| |
| protected void initRunnableAdapterL() { |
| this.fCurrentPrompt= this.fDefaultPrompt= this.workspaceData.getDefaultPrompt(); |
| this.fLineSeparator= this.workspaceData.getLineSeparator(); |
| } |
| |
| |
| @Override |
| public final ToolController getController() { |
| return this; |
| } |
| |
| |
| private RunnableData createRunnableData(final ToolRunnable runnable, final Section queueSection) { |
| return new RunnableData(runnable, getSubmitTypeL(runnable), queueSection); |
| } |
| |
| private void setCurrentRunnable(final RunnableData runnable) { |
| this.currentRunnable= runnable; |
| this.currentSubmitType= runnable.submitType; |
| } |
| |
| protected SubmitType getSubmitTypeL(final ToolRunnable runnable) { |
| if (runnable instanceof ConsoleRunnable) { |
| return ((ConsoleRunnable) runnable).getSubmitType(); |
| } |
| else if (runnable instanceof SystemRunnable) { |
| return SubmitType.OTHER; |
| } |
| else { |
| return SubmitType.TOOLS; |
| } |
| } |
| |
| @Override |
| public ToolRunnable getCurrentRunnable() { |
| return this.currentRunnable.runnable; |
| } |
| |
| public Queue.Section getCurrentQueueSection() { |
| return this.currentRunnable.queueSection; |
| } |
| |
| public SubmitType getCurrentSubmitType() { |
| return this.currentSubmitType; |
| } |
| |
| |
| public String getProperty(final String key) { |
| return null; |
| } |
| |
| @Override |
| public final void refreshWorkspaceData(final int options, |
| final ProgressMonitor m) throws StatusException { |
| this.workspaceData.controlRefresh(options, this, m); |
| } |
| |
| @Override |
| public ToolWorkspace getWorkspaceData() { |
| return this.workspaceData; |
| } |
| |
| @Override |
| public boolean isDefaultPrompt() { |
| return (this.fDefaultPrompt == this.fCurrentPrompt); |
| } |
| |
| @Override |
| public Prompt getPrompt() { |
| return this.fCurrentPrompt; |
| } |
| |
| protected void setCurrentPromptL(final Prompt prompt) { |
| this.fCurrentPrompt= prompt; |
| this.workspaceData.controlSetCurrentPrompt(prompt, this.status); |
| } |
| |
| protected void setDefaultPromptTextL(final String text) { |
| this.fDefaultPrompt= new Prompt(text, ConsoleService.META_PROMPT_DEFAULT); |
| this.workspaceData.controlSetDefaultPrompt(this.fDefaultPrompt); |
| } |
| |
| protected void setLineSeparatorL(final String newSeparator) { |
| this.fLineSeparator= newSeparator; |
| this.workspaceData.controlSetLineSeparator(newSeparator); |
| } |
| |
| protected void setFileSeparatorL(final char newSeparator) { |
| this.workspaceData.controlSetFileSeparator(newSeparator); |
| } |
| |
| protected void setWorkspaceDirL(final IFileStore directory) { |
| this.workspaceData.controlSetWorkspaceDir(directory); |
| } |
| |
| protected void setRemoteWorkspaceDirL(final IPath directory) { |
| this.workspaceData.controlSetRemoteWorkspaceDir(directory); |
| } |
| |
| |
| public final int getChangeStamp() { |
| return this.currentStamp; |
| } |
| |
| private void touchChangeStamp() { |
| this.currentStamp= this.changeStamp= ((this.changeStamp + 1) & ~(1 << 31)); |
| } |
| |
| private void enableHotStamp() { |
| this.currentStamp= this.hotStamp= ((this.hotStamp + 1) | (1 << 31)); |
| } |
| |
| private void disableHotStamp() { |
| this.currentStamp= this.changeStamp; |
| } |
| |
| public void briefAboutToChange() { |
| touchChangeStamp(); |
| } |
| |
| public void briefChanged(final int flags) { |
| briefChanged(null, flags); |
| } |
| |
| public void briefChanged(final Object obj, final int flags) { |
| touchChangeStamp(); |
| this.workspaceData.controlBriefChanged(obj, flags); |
| if (DEBUG_LOG_STATE) { |
| logChanged(); |
| } |
| } |
| |
| |
| private void logEvents(final String label, final ToolStatus status) { |
| final ToStringBuilder sb= new ObjectUtils.ToStringBuilder(label); |
| sb.addProp("status", this.status); //$NON-NLS-1$ |
| sb.addProp("changeStamp", this.changeStamp); //$NON-NLS-1$ |
| sb.addProp("events", this.queue.internal_getEventList()); //$NON-NLS-1$ |
| NicoCorePlugin.log(new InfoStatus(NicoCore.BUNDLE_ID, sb.toString())); |
| } |
| |
| private void logChanged() { |
| final ToStringBuilder sb= new ObjectUtils.ToStringBuilder("changed"); //$NON-NLS-1$ |
| sb.addProp("changeStamp", this.changeStamp); //$NON-NLS-1$ |
| NicoCorePlugin.log(new InfoStatus(NicoCore.BUNDLE_ID, sb.toString())); |
| } |
| |
| |
| @Override |
| public void submitToConsole(final String input, |
| final ProgressMonitor m) throws StatusException { |
| this.fCurrentInput= input; |
| doBeforeSubmitL(); |
| doSubmitL(m); |
| } |
| |
| protected void doBeforeSubmitL() { |
| final ToolStreamProxy streams= getStreams(); |
| final SubmitType submitType= getCurrentSubmitType(); |
| |
| streams.getInfoStreamMonitor().append(this.fCurrentPrompt.text, submitType, |
| this.fCurrentPrompt.meta); |
| streams.getInputStreamMonitor().append(this.fCurrentInput, submitType, |
| (this.fCurrentPrompt.meta & ConsoleService.META_HISTORY_DONTADD) ); |
| streams.getInputStreamMonitor().append(this.workspaceData.getLineSeparator(), submitType, |
| ConsoleService.META_HISTORY_DONTADD); |
| } |
| |
| protected abstract void doSubmitL(final ProgressMonitor m) throws StatusException; |
| |
| protected StatusException cancelTask() { |
| return new StatusException(new CancelStatus(NicoCore.BUNDLE_ID, |
| Messages.ToolRunnable_error_RuntimeError_message, |
| null )); |
| } |
| |
| protected abstract void doQuitL(final ProgressMonitor m) throws StatusException; |
| |
| } |