| /*=============================================================================# |
| # Copyright (c) 2006, 2021 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 java.util.ArrayDeque; |
| import java.util.ArrayList; |
| import java.util.Deque; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| |
| import org.eclipse.debug.core.DebugEvent; |
| import org.eclipse.debug.core.DebugPlugin; |
| import org.eclipse.osgi.util.NLS; |
| |
| import org.eclipse.statet.jcommons.collections.ImCollections; |
| import org.eclipse.statet.jcommons.collections.ImList; |
| import org.eclipse.statet.jcommons.lang.NonNull; |
| import org.eclipse.statet.jcommons.lang.NonNullByDefault; |
| import org.eclipse.statet.jcommons.lang.Nullable; |
| import org.eclipse.statet.jcommons.status.ErrorStatus; |
| import org.eclipse.statet.jcommons.status.InfoStatus; |
| import org.eclipse.statet.jcommons.status.OkStatus; |
| import org.eclipse.statet.jcommons.status.Status; |
| import org.eclipse.statet.jcommons.ts.core.RunnableStatus; |
| import org.eclipse.statet.jcommons.ts.core.SystemRunnable; |
| import org.eclipse.statet.jcommons.ts.core.ToolQueue; |
| import org.eclipse.statet.jcommons.ts.core.ToolRunnable; |
| |
| import org.eclipse.statet.internal.nico.core.Messages; |
| import org.eclipse.statet.nico.core.NicoCore; |
| |
| |
| /** |
| * Queue with ToolRunnable waiting to be processed by the tool/controller. |
| * |
| * Usage: You get your queue via accessor of the ToolProcess. |
| * |
| * DebugEvents for a lifecycle of an entry:<pre> |
| * CHANGE (CONTENT) |
| * [ENTRIES_ADDED] |
| * | |
| * +--------------------+---------------------+ |
| * | | | |
| * | | CHANGE (CONTENT) |
| * | | [ENTRY_START_PROCESSING] |
| * CHANGE (CONTENT) | | |
| * [ENTRIES_DELETE] | CHANGE (CONTENT) |
| * | [ENTRY_FINISH_PROCESSING] |
| * TERMINATE |
| * [ENTRIES_ABANDONED] |
| * </pre> |
| * The events of this type are sended by the queue (source element). |
| * |
| */ |
| @NonNullByDefault |
| public final class Queue implements ToolQueue { |
| |
| |
| public static final byte INIT_STATE= 1; |
| public static final byte PROCESSING_STATE= 2; |
| public static final byte IDLING_STATE= 3; |
| public static final byte PAUSED_STATE= 5; |
| public static final byte TERMINATED_STATE= 6; |
| |
| |
| /** |
| * Constant for detail of a DebugEvent, sending the complete queue. |
| * This does not signalising, that the queue has changed. |
| * <p> |
| * The queue entries (<code>List<ToolRunnable></code>) are attached as |
| * data to this event. The source of the event is the ToolProcess. |
| * <p> |
| * Usage: Events of this type are sended by the ToolProcess/its queue. |
| * The constant is applicable for DebugEvents of kind |
| * <code>MODEL_SPECIFIC</code>.</p> |
| */ |
| public static final int QUEUE_INFO= 1; |
| |
| public static final int STATE_REQUEST= 5; |
| |
| |
| /** |
| * TaskDelta for events of the queue. |
| * |
| * Type is a event type of {@link ToolRunnable} events |
| */ |
| public static final class TaskDelta { |
| |
| public final int type; |
| public final int position; |
| |
| /** |
| * One or multiple runnable effected by this event. |
| * STARTING and FINISHING events have always only a single item. |
| */ |
| public final ImList<ToolRunnable> data; |
| |
| private TaskDelta(final int type, final int position, final ImList<ToolRunnable> data) { |
| this.type= type; |
| this.position= position; |
| this.data= data; |
| } |
| |
| } |
| |
| public static final boolean isStateChange(final DebugEvent event) { |
| return (event.getKind() == DebugEvent.CHANGE && event.getDetail() == DebugEvent.STATE); |
| } |
| |
| public static final boolean isStateRequest(final DebugEvent event) { |
| return (event.getKind() == DebugEvent.MODEL_SPECIFIC && event.getDetail() == STATE_REQUEST); |
| } |
| |
| public static final class StateDelta { |
| |
| public final byte oldState; |
| public final byte newState; |
| |
| private StateDelta(final byte oldState, final byte newState) { |
| this.oldState= oldState; |
| this.newState= newState; |
| } |
| |
| } |
| |
| private static final int toRequestState(final int state) { |
| return (state == IDLING_STATE) ? PROCESSING_STATE : state; |
| } |
| |
| |
| public static final int IF_ABSENT= 1 << 0; |
| |
| |
| private static class RunnableInfoStatus extends InfoStatus implements RunnableStatus { |
| |
| private final ToolRunnable runnable; |
| |
| public RunnableInfoStatus(final int code, final String message, final ToolRunnable runnable) { |
| super(NicoCore.BUNDLE_ID, code, message); |
| this.runnable= runnable; |
| } |
| |
| @Override |
| public ToolRunnable getRunnable() { |
| return this.runnable; |
| } |
| |
| } |
| |
| private static class RunnableOkStatus extends OkStatus implements RunnableStatus { |
| |
| private final ToolRunnable runnable; |
| |
| public RunnableOkStatus(final String message, final ToolRunnable runnable) { |
| super(NicoCore.BUNDLE_ID, "OK"); |
| this.runnable= runnable; |
| } |
| |
| @Override |
| public ToolRunnable getRunnable() { |
| return this.runnable; |
| } |
| |
| } |
| |
| |
| private static final Status ADDED_STATUS= new OkStatus(NicoCore.BUNDLE_ID, "Added."); |
| |
| public static final Status ALREADY_PRESENT_STATUS= new InfoStatus(NicoCore.BUNDLE_ID, "Already in queue."); |
| |
| |
| private static ToolRunnable checkRunnable(final @Nullable ToolRunnable runnable) { |
| if (runnable == null) { |
| throw new NullPointerException("runnable"); //$NON-NLS-1$ |
| } |
| return runnable; |
| } |
| |
| private static ImList<ToolRunnable> checkRunnableList(final @Nullable ToolRunnable runnable) { |
| if (runnable == null) { |
| throw new NullPointerException("runnable"); //$NON-NLS-1$ |
| } |
| return ImCollections.newList(runnable); |
| } |
| |
| private static ImList<ToolRunnable> checkRunnableList(final @Nullable List<ToolRunnable> runnables) { |
| if (runnables == null) { |
| throw new NullPointerException("runnables"); //$NON-NLS-1$ |
| } |
| final ImList<ToolRunnable> finalRunnables= ImCollections.toList(runnables); |
| final int i= finalRunnables.indexOf(null); |
| if (i >= 0) { |
| throw new NullPointerException("runnable[" + i + ']'); //$NON-NLS-1$ |
| } |
| return finalRunnables; |
| } |
| |
| |
| static final int RUN_NONE= -2; |
| static final int RUN_SUSPEND= -1; |
| static final int RUN_CONTROL= 1; |
| static final int RUN_HOT= 2; |
| static final int RUN_OTHER= 3; |
| static final int RUN_DEFAULT= 4; |
| |
| |
| private static class RankedItem { |
| |
| final ToolRunnable runnable; |
| final int rank; |
| |
| public RankedItem(final ToolRunnable runnable, final int rank) { |
| this.runnable= runnable; |
| this.rank= rank; |
| } |
| |
| |
| @Override |
| public int hashCode() { |
| return this.runnable.hashCode(); |
| } |
| |
| @Override |
| public boolean equals(final Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| final RankedItem other= (RankedItem) obj; |
| return (this.runnable.equals(other.runnable) ); |
| } |
| |
| } |
| |
| |
| public static interface Section { |
| |
| } |
| |
| private static abstract class SubQueue implements Section { |
| |
| protected final LinkedList<ToolRunnable> list= new LinkedList<>(); |
| |
| private @Nullable SubQueue next; |
| |
| private boolean state; |
| |
| |
| public abstract int getAllSize(); |
| |
| public abstract int getAll(final ToolRunnable[] array, final int i); |
| |
| public int getStartIdx() { |
| int idx= 0; |
| SubQueue next= this.next; |
| while (next != null) { |
| idx+= next.getAllSize(); |
| next= next.next; |
| } |
| return idx; |
| } |
| |
| public int getAppendIdx() { |
| return getStartIdx() + this.list.size(); |
| } |
| |
| public void append(final ImList<ToolRunnable> runnables) { |
| if (runnables.size() == 1) { |
| this.list.add(runnables.get(0)); |
| } |
| else { |
| this.list.addAll(runnables); |
| } |
| } |
| |
| public void append(final ToolRunnable runnable) { |
| this.list.add(runnable); |
| } |
| |
| public void dispose() { |
| this.state= true; |
| |
| this.list.clear(); |
| |
| this.next= null; |
| } |
| |
| public boolean isDispose() { |
| return this.state; |
| } |
| |
| } |
| |
| private static final class TopLevelQueue extends SubQueue { |
| |
| |
| public TopLevelQueue() { |
| } |
| |
| |
| @Override |
| public int getAllSize() { |
| return this.list.size(); |
| } |
| |
| @Override |
| public int getAll(final ToolRunnable[] array, int i) { |
| for (final ToolRunnable runnable : this.list) { |
| array[i++]= runnable; |
| } |
| return i; |
| } |
| |
| } |
| |
| private static final class InsertQueue extends SubQueue { |
| |
| protected final ToolRunnable insertRunnable; |
| |
| |
| public InsertQueue(final ToolRunnable insertRunnable) { |
| this.insertRunnable= insertRunnable; |
| } |
| |
| |
| @Override |
| public int getAllSize() { |
| return this.list.size() + 1; |
| } |
| |
| @Override |
| public int getAll(final ToolRunnable[] array, int i) { |
| for (final ToolRunnable runnable : this.list) { |
| array[i++]= runnable; |
| } |
| array[i++]= this.insertRunnable; |
| return i; |
| } |
| |
| } |
| |
| private final List<SubQueue> sectionStack= new ArrayList<>(); |
| private final SubQueue topLevelSection; // sectionStack.first |
| private SubQueue currentSection; // sectionStack.last |
| private @Nullable ImList<ToolRunnable> singleIOCache= null; |
| |
| private final Deque<ImList<ToolRunnable>> finishedExpected= new ArrayDeque<>(); |
| private final List<DebugEvent> eventList= new ArrayList<>(5); |
| |
| private final ToolProcess process; |
| |
| private boolean resetOnIdle= true; |
| private final List<RankedItem> onIdleList= new ArrayList<>(); |
| private final LinkedList<ToolRunnable> currentIdleList= new LinkedList<>(); |
| |
| private final LinkedList<ToolRunnable> hotList= new LinkedList<>(); |
| |
| private byte state; |
| private byte stateRequest= PROCESSING_STATE; |
| |
| |
| Queue(final ToolProcess process) { |
| this.process= process; |
| |
| this.topLevelSection= new TopLevelQueue(); |
| this.sectionStack.add(this.topLevelSection); |
| this.currentSection= this.topLevelSection; |
| } |
| |
| |
| private final Status acceptSubmit(final ToolStatus toolStatus) { |
| if (toolStatus == ToolStatus.TERMINATED) { |
| return new ErrorStatus(NicoCore.BUNDLE_ID, -1, |
| NLS.bind(Messages.ToolController_ToolTerminated_message, this.process.getLabel(0)), |
| null ); |
| } |
| return ADDED_STATUS; |
| } |
| |
| private final Status acceptSubmit(final ToolStatus toolStatus, final SubQueue section) { |
| if (toolStatus == ToolStatus.TERMINATED) { |
| return new ErrorStatus(NicoCore.BUNDLE_ID, -1, |
| NLS.bind(Messages.ToolController_ToolTerminated_message, this.process.getLabel(0)), |
| null ); |
| } |
| if (section.isDispose()) { |
| return new ErrorStatus(NicoCore.BUNDLE_ID, -1, |
| NLS.bind(Messages.ToolController_QueueSectionTerminated_message, this.process.getLabel(0)), |
| null ); |
| } |
| return ADDED_STATUS; |
| } |
| |
| public synchronized void sendElements() { |
| checkIOCache(); |
| final ImList<ToolRunnable> runnables= internal_createCompleteList(); |
| final DebugEvent event= new DebugEvent(this, DebugEvent.MODEL_SPECIFIC, QUEUE_INFO); |
| event.setData(runnables); |
| this.eventList.add(event); |
| internal_fireEvents(); |
| } |
| |
| public Section getTopLevelSection() { |
| return this.topLevelSection; |
| } |
| |
| public synchronized Section getCurrentSection() { |
| return this.currentSection; |
| } |
| |
| |
| public synchronized int getCurrentSize() { |
| if (this.singleIOCache != null) { |
| return 1; |
| } |
| return this.currentSection.list.size(); |
| } |
| |
| /** |
| * Submits the runnable for the tool. |
| * <p> |
| * The runnable will be added to the queue and will be run, if it's its turn. |
| * |
| * @param runnable the runnable to add |
| * @return the status of the queue operation. |
| */ |
| @Override |
| public Status add(final ToolRunnable runnable) { |
| final ImList<ToolRunnable> checkedRunnables= checkRunnableList(runnable); |
| |
| return doAdd(checkedRunnables, null); |
| } |
| |
| /** |
| * Submits the runnables for the tool. |
| * <p> |
| * The runnables will be added en block to the queue and will be runned, if it's its turn. |
| * |
| * @param runnables the runnables to add. |
| * @return the status of the queue operation. |
| */ |
| public Status add(final List<ToolRunnable> runnables) { |
| final ImList<ToolRunnable> checkedRunnables= checkRunnableList(runnables); |
| |
| return doAdd(checkedRunnables, null); |
| } |
| |
| /** |
| * Submits the runnable for the tool. |
| * <p> |
| * The runnable will be added to the queue in the specified section |
| * and will be run, if it's its turn. |
| * |
| * @param runnable the runnable to add |
| * @param section where to add the runnable, <code>null</code> for current section |
| * @param strategy flags {@link #IF_ABSENT} |
| * @return the status of the queue operation. |
| */ |
| public Status add(final ToolRunnable runnable, |
| final @Nullable Section section, final int strategy) { |
| final ToolRunnable checkedRunnable= checkRunnable(runnable); |
| |
| return doAdd(checkedRunnable, (SubQueue) section, strategy); |
| } |
| |
| /** |
| * Submits the runnables for the tool. |
| * <p> |
| * The runnables will be added en block to the queue in the specified section |
| * and will be runned, if it's its turn. |
| * |
| * @param runnables the runnables to add. |
| * @param section where to add the runnabler, <code>null</code> for current section |
| * @return the status of the queue operation. |
| */ |
| public Status add(final List<ToolRunnable> runnables, |
| final @Nullable Section section) { |
| final ImList<ToolRunnable> checkedRunnables= checkRunnableList(runnables); |
| |
| return doAdd(checkedRunnables, (SubQueue) section); |
| } |
| |
| private synchronized Status doAdd(final ImList<ToolRunnable> runnables, |
| @Nullable SubQueue section) { |
| if (section == null) { |
| section= this.currentSection; |
| } |
| |
| final ToolStatus toolStatus= this.process.getToolStatus(); |
| final Status status= acceptSubmit(toolStatus, section); |
| if (status.getSeverity() < Status.ERROR) { |
| if (toolStatus.isWaiting()) { |
| internal_add(runnables, section, true); |
| notifyAll(); |
| } |
| else { |
| internal_add(runnables, section, false); |
| } |
| } |
| return status; |
| } |
| |
| private synchronized Status doAdd(final ToolRunnable runnable, |
| @Nullable SubQueue section, final int strategy) { |
| if (section == null) { |
| section= this.currentSection; |
| } |
| |
| final ToolStatus toolStatus= this.process.getToolStatus(); |
| final Status status= acceptSubmit(toolStatus, section); |
| if (status.getSeverity() < Status.ERROR) { |
| if (strategy != 0) { |
| checkIOCache(); |
| |
| int idx; |
| if ((strategy & IF_ABSENT) != 0 && (idx= section.list.indexOf(runnable)) >= 0) { |
| return new RunnableInfoStatus(0, ALREADY_PRESENT_STATUS.getMessage(), |
| section.list.get(idx) ); |
| } |
| } |
| |
| if (toolStatus.isWaiting()) { |
| internal_add(ImCollections.newList(runnable), section, true); |
| notifyAll(); |
| } |
| else { |
| internal_add(ImCollections.newList(runnable), section, false); |
| } |
| return new RunnableOkStatus(ADDED_STATUS.getMessage(), runnable); |
| } |
| return status; |
| } |
| |
| @Override |
| public void remove(final ToolRunnable runnable) { |
| final ImList<ToolRunnable> checkedRunnables= checkRunnableList(runnable); |
| |
| synchronized (this) { |
| internal_remove(checkedRunnables); |
| |
| internal_fireEvents(); |
| } |
| } |
| |
| public void remove(final List<ToolRunnable> runnables) { |
| final ImList<ToolRunnable> checkedRunnables= checkRunnableList(runnables); |
| |
| synchronized (this) { |
| internal_remove(checkedRunnables); |
| |
| internal_fireEvents(); |
| } |
| } |
| |
| public void move(final ImList<ToolRunnable> runnables, final Queue to) { |
| final ImList<ToolRunnable> checkedRunnables= checkRunnableList(runnables); |
| if (to == null) { |
| throw new NullPointerException("to"); //$NON-NLS-1$ |
| } |
| |
| final ImList<ToolRunnable> finalRunnables; |
| synchronized (this) { |
| finalRunnables= internal_moveFrom(checkedRunnables); |
| } |
| |
| synchronized (to) { |
| to.internal_moveTo(finalRunnables, to.currentSection); |
| |
| to.notifyAll(); |
| } |
| } |
| |
| public void moveAll(final Queue to) { |
| if (to == null) { |
| throw new NullPointerException("to"); //$NON-NLS-1$ |
| } |
| final ImList<ToolRunnable> finalRunnables; |
| synchronized (this) { |
| checkIOCache(); |
| |
| { final List<ToolRunnable> removed= new ArrayList<>(internal_getCompleteSize()); |
| for (final Iterator<ToolRunnable> iter= this.topLevelSection.list.iterator(); iter.hasNext();) { |
| final ToolRunnable runnable= iter.next(); |
| if (runnable.changed(ToolRunnable.MOVING_FROM, this.process)) { |
| iter.remove(); |
| removed.add(runnable); |
| } |
| } |
| finalRunnables= ImCollections.toList(removed); |
| } |
| |
| addContentChangeEvent(new TaskDelta(ToolRunnable.MOVING_FROM, -1, finalRunnables)); |
| |
| internal_fireEvents(); |
| } |
| |
| synchronized (to) { |
| to.internal_moveTo(finalRunnables, to.topLevelSection); |
| |
| to.notifyAll(); |
| } |
| } |
| |
| public Status addOnIdle(final SystemRunnable runnable, final int rank) { |
| final ToolRunnable checkedRunnable= checkRunnable(runnable); |
| |
| synchronized (this) { |
| final ToolStatus toolStatus= this.process.getToolStatus(); |
| final Status status= acceptSubmit(toolStatus); |
| if (status.getSeverity() < Status.ERROR) { |
| final RankedItem item= new RankedItem(checkedRunnable, rank); |
| |
| int idx= this.onIdleList.indexOf(item); |
| if (idx >= 0 && this.onIdleList.get(idx).rank != rank) { |
| this.onIdleList.remove(idx); |
| if (!this.resetOnIdle) { |
| this.currentIdleList.remove(item.runnable); |
| } |
| idx= -1; |
| } |
| if (idx < 0) { |
| idx= 0; |
| for (; idx < this.onIdleList.size(); idx++) { |
| if (this.onIdleList.get(idx).rank > rank) { |
| break; |
| } |
| } |
| this.onIdleList.add(idx, item); |
| |
| if (!this.resetOnIdle) { |
| if (idx == this.onIdleList.size() - 1) { // last |
| this.currentIdleList.add(item.runnable); |
| } |
| else { |
| final RankedItem next= this.onIdleList.get(idx + 1); |
| final int nextIdx= this.currentIdleList.indexOf(next.runnable); |
| if (nextIdx >= 0) { |
| this.currentIdleList.add(nextIdx, item.runnable); |
| } |
| } |
| } |
| } |
| notifyAll(); |
| } |
| return status; |
| } |
| } |
| |
| public void removeOnIdle(final ToolRunnable runnable) { |
| final ToolRunnable checkedRunnable= checkRunnable(runnable); |
| |
| synchronized (this) { |
| this.onIdleList.remove(new RankedItem(checkedRunnable, 0)); |
| this.currentIdleList.remove(runnable); |
| } |
| } |
| |
| @Override |
| public boolean isHotSupported() { |
| return true; |
| } |
| |
| @Override |
| public Status addHot(final ToolRunnable runnable) { |
| return addHot(runnable, 0); |
| } |
| |
| public Status addHot(final ToolRunnable runnable, final int strategy) { |
| final ToolRunnable checkedRunnable= checkRunnable(runnable); |
| |
| synchronized (this) { |
| final ToolStatus toolStatus= this.process.getToolStatus(); |
| final Status status= acceptSubmit(toolStatus); |
| if (status.getSeverity() < Status.ERROR) { |
| if ((strategy & IF_ABSENT) != 0) { |
| if (this.hotList.contains(checkedRunnable)) { |
| return ALREADY_PRESENT_STATUS; |
| } |
| } |
| this.hotList.add(checkedRunnable); |
| if (this.hotList.size() > 0) { |
| notifyAll(); |
| final ToolController controller= this.process.getController(); |
| if (controller != null) { |
| controller.scheduleHotMode(); |
| } |
| } |
| } |
| return status; |
| } |
| } |
| |
| @Override |
| public void removeHot(final ToolRunnable runnable) { |
| final ToolRunnable checkedRunnable= checkRunnable(runnable); |
| synchronized (this) { |
| if (this.hotList.remove(checkedRunnable)) { |
| runnable.changed(ToolRunnable.REMOVING_FROM, this.process); |
| } |
| } |
| } |
| |
| |
| private void internal_add(final ImList<ToolRunnable> runnables, final SubQueue section, |
| final boolean allowCache) { |
| if (allowCache && this.singleIOCache == null |
| && runnables.size() == 1 |
| && section == this.currentSection && this.currentSection.list.isEmpty() ) { |
| this.singleIOCache= runnables; |
| return; |
| } |
| |
| checkIOCache(); |
| |
| final int idx= section.getAppendIdx(); |
| section.append(runnables); |
| addContentChangeEvent(new TaskDelta(ToolRunnable.ADDING_TO, idx, runnables)); |
| |
| internal_fireEvents(); |
| } |
| |
| private void internal_remove(final ImList<ToolRunnable> runnables) { |
| checkIOCache(); |
| |
| final ImList<ToolRunnable> finalRunnables; |
| { final List<ToolRunnable> removed= new ArrayList<>(runnables.size()); |
| |
| ITER_RUNNABLE: for (final ToolRunnable runnable : runnables) { |
| for (int iSection= this.sectionStack.size() - 1; iSection >= 0; iSection--) { |
| final SubQueue section= this.sectionStack.get(iSection); |
| final int idx= section.list.indexOf(runnable); |
| if (idx >= 0) { |
| if (runnable.changed(ToolRunnable.REMOVING_FROM, this.process)) { |
| section.list.remove(idx); |
| removed.add(runnable); |
| } |
| continue ITER_RUNNABLE; |
| } |
| } |
| } |
| finalRunnables= ImCollections.toList(removed); |
| } |
| |
| addContentChangeEvent(new TaskDelta(ToolRunnable.REMOVING_FROM, -1, finalRunnables)); |
| |
| internal_fireEvents(); |
| } |
| |
| private ImList<ToolRunnable> internal_moveFrom(final ImList<ToolRunnable> runnables) { |
| checkIOCache(); |
| |
| final ImList<ToolRunnable> finalRunnables; |
| { final List<ToolRunnable> removed= new ArrayList<>(runnables.size()); |
| |
| ITER_RUNNABLE: for (final ToolRunnable runnable : runnables) { |
| for (int iSection= this.sectionStack.size() - 1; iSection >= 0; iSection--) { |
| final SubQueue section= this.sectionStack.get(iSection); |
| final int idx= section.list.indexOf(runnable); |
| if (idx >= 0) { |
| if (runnable.changed(ToolRunnable.MOVING_FROM, this.process)) { |
| section.list.remove(idx); |
| removed.add(runnable); |
| } |
| continue ITER_RUNNABLE; |
| } |
| } |
| } |
| finalRunnables= ImCollections.toList(removed); |
| } |
| |
| addContentChangeEvent(new TaskDelta(ToolRunnable.MOVING_FROM, -1, finalRunnables)); |
| |
| internal_fireEvents(); |
| |
| return finalRunnables; |
| } |
| |
| private void internal_moveTo(final ImList<ToolRunnable> runnables, final SubQueue section) { |
| checkIOCache(); |
| |
| final int idx= section.getAppendIdx(); |
| section.append(runnables); |
| addContentChangeEvent(new TaskDelta(ToolRunnable.MOVING_TO, idx, runnables)); |
| |
| internal_fireEvents(); |
| } |
| |
| |
| Section internal_addInsert(final ToolRunnable runnable) { |
| assert (runnable != null); |
| |
| checkIOCache(); |
| |
| final SubQueue section= new InsertQueue(runnable); |
| this.sectionStack.add(section); |
| this.currentSection.next= section; |
| this.currentSection= section; |
| addContentChangeEvent(new TaskDelta(ToolRunnable.ADDING_TO, 0, ImCollections.newList(runnable))); |
| |
| internal_resetOnIdle(); |
| |
| internal_fireEvents(); |
| |
| return section; |
| } |
| |
| void internal_removeInsert(final Section section) { |
| assert (section != null); |
| |
| checkIOCache(); |
| |
| final int sectionIdx= this.sectionStack.indexOf(section); |
| if (sectionIdx < 0) { |
| return; |
| } |
| if (sectionIdx == 0) { |
| throw new IllegalArgumentException(); |
| } |
| |
| final ImList<ToolRunnable> finalRunnables; |
| { final ToolRunnable[] runnables= new @NonNull ToolRunnable[ |
| internal_getSize(sectionIdx, this.sectionStack.size()) ]; |
| int i= 0; |
| for (int iSection= this.sectionStack.size() - 1; iSection >= sectionIdx; iSection--) { |
| final SubQueue removedSection= this.sectionStack.remove(iSection); |
| i= removedSection.getAll(runnables, i); |
| removedSection.dispose(); |
| } |
| this.currentSection= this.sectionStack.get(this.sectionStack.size() - 1); |
| this.currentSection.next= null; |
| |
| finalRunnables= ImCollections.newList(runnables); |
| } |
| for (final ToolRunnable runnable : finalRunnables) { |
| // runnable.changed(ToolRunnable.BEING_ABANDONED, this.process); |
| if (!runnable.changed(ToolRunnable.REMOVING_FROM, this.process)) { |
| runnable.changed(ToolRunnable.BEING_ABANDONED, this.process); |
| } |
| } |
| |
| addContentChangeEvent(new TaskDelta(ToolRunnable.REMOVING_FROM, -1, finalRunnables)); |
| |
| internal_resetOnIdle(); |
| |
| internal_fireEvents(); |
| } |
| |
| |
| void internal_scheduleIdle(final ToolRunnable runnable) { |
| final ToolRunnable checkedRunnable= checkRunnable(runnable); |
| |
| this.currentIdleList.add(checkedRunnable); |
| } |
| |
| void internal_removeIdle(final ToolRunnable runnable) { |
| final ToolRunnable checkedRunnable= checkRunnable(runnable); |
| |
| this.currentIdleList.remove(checkedRunnable); |
| } |
| |
| |
| void internal_check() { |
| checkIOCache(); |
| internal_fireEvents(); |
| } |
| |
| void internal_resetOnIdle() { |
| this.resetOnIdle= true; |
| } |
| |
| private void internal_resetIdleList() { |
| this.resetOnIdle= false; |
| this.currentIdleList.clear(); |
| for (int i= 0; i < this.onIdleList.size(); i++) { |
| this.currentIdleList.add(this.onIdleList.get(i).runnable); |
| } |
| } |
| |
| int internal_next() { |
| if (!this.hotList.isEmpty()) { |
| return RUN_HOT; |
| } |
| final ToolRunnable runnable; |
| if (this.singleIOCache != null) { |
| runnable= this.singleIOCache.get(0); |
| } |
| else if (!this.currentSection.list.isEmpty()) { |
| runnable= this.currentSection.list.peekFirst(); |
| } |
| else { |
| if (this.resetOnIdle) { |
| internal_resetIdleList(); |
| } |
| if (!this.currentIdleList.isEmpty()) { |
| runnable= this.currentIdleList.peekFirst(); |
| } |
| else { |
| return RUN_NONE; |
| } |
| } |
| return (runnable instanceof SystemRunnable) ? |
| RUN_OTHER : RUN_DEFAULT; |
| } |
| |
| /** After check with {@link #internal_next()} */ |
| ToolRunnable internal_poll() { |
| final @NonNull ImList<ToolRunnable> finalRunnable; |
| if (this.singleIOCache != null) { |
| finalRunnable= this.singleIOCache; |
| final int idx= this.currentSection.getAppendIdx(); |
| addContentChangeEvent(new TaskDelta(ToolRunnable.ADDING_TO, idx, finalRunnable)); |
| this.singleIOCache= null; |
| internal_resetOnIdle(); |
| } |
| else if (!this.currentSection.list.isEmpty()) { |
| finalRunnable= ImCollections.newList(this.currentSection.list.pollFirst()); |
| internal_resetOnIdle(); |
| } |
| else { |
| finalRunnable= ImCollections.newList(this.currentIdleList.pollFirst()); |
| } |
| addContentChangeEvent(new TaskDelta(ToolRunnable.STARTING, -1, finalRunnable)); |
| |
| internal_fireEvents(); |
| this.finishedExpected.push(finalRunnable); |
| return finalRunnable.get(0); |
| } |
| |
| boolean internal_nextHot() { |
| return !this.hotList.isEmpty(); |
| } |
| |
| @Nullable ToolRunnable internal_pollHot() { |
| return this.hotList.pollFirst(); |
| } |
| |
| /** |
| * Not necessary in synchronized block |
| */ |
| void internal_onFinished(final ToolRunnable runnable, final int detail) { |
| assert (runnable == this.finishedExpected.peek().get(0)); |
| |
| addContentChangeEvent(new TaskDelta(detail, -1, this.finishedExpected.poll())); |
| } |
| |
| private int internal_getCompleteSize() { |
| int size= 0; |
| for (int iSection= this.sectionStack.size() - 1; iSection >= 0; iSection--) { |
| size+= this.sectionStack.get(iSection).getAllSize(); |
| } |
| return size; |
| } |
| |
| private int internal_getSize(final int startSection, final int endSection) { |
| int size= 0; |
| for (int iSection= endSection - 1; iSection >= startSection; iSection--) { |
| size+= this.sectionStack.get(iSection).getAllSize(); |
| } |
| return size; |
| } |
| |
| private ImList<ToolRunnable> internal_createCompleteList() { |
| final ToolRunnable[] runnables= new @NonNull ToolRunnable[ |
| internal_getCompleteSize() ]; |
| int i= 0; |
| for (int iSection= this.sectionStack.size() - 1; iSection >= 0; iSection--) { |
| i= this.sectionStack.get(iSection).getAll(runnables, i); |
| } |
| return ImCollections.newList(runnables); |
| } |
| |
| List<ToolRunnable> internal_getList() { |
| internal_check(); |
| return internal_createCompleteList(); |
| } |
| |
| List<ToolRunnable> internal_getCurrentList() { |
| internal_check(); |
| return this.currentSection.list; |
| } |
| |
| |
| public synchronized boolean isRequested(final byte state) { |
| return (this.state == state || this.stateRequest == state); |
| } |
| |
| public synchronized boolean pause() { |
| if (this.state == TERMINATED_STATE) { |
| return false; |
| } |
| final byte oldState= this.stateRequest; |
| if (oldState != PAUSED_STATE) { |
| this.stateRequest= PAUSED_STATE; |
| addStateEvent(DebugEvent.MODEL_SPECIFIC, STATE_REQUEST, |
| new StateDelta(oldState, PAUSED_STATE)); |
| internal_fireEvents(); |
| notifyAll(); |
| } |
| return true; |
| } |
| |
| public synchronized boolean resume() { |
| if (this.state == TERMINATED_STATE) { |
| return false; |
| } |
| final byte oldState= this.stateRequest; |
| if (oldState != PROCESSING_STATE) { |
| this.stateRequest= PROCESSING_STATE; |
| addStateEvent(DebugEvent.MODEL_SPECIFIC, STATE_REQUEST, |
| new StateDelta(oldState, PROCESSING_STATE) ); |
| internal_fireEvents(); |
| notifyAll(); |
| } |
| return true; |
| } |
| |
| void internal_onStatusChanged(final ToolStatus newStatus) { |
| final byte oldState= this.state; |
| switch (newStatus) { |
| case STARTED_IDLING: |
| case STARTED_SUSPENDED: |
| this.state= IDLING_STATE; |
| break; |
| case STARTED_PAUSED: |
| this.state= PAUSED_STATE; |
| break; |
| case TERMINATED: |
| this.state= TERMINATED_STATE; |
| break; |
| default: |
| this.state= PROCESSING_STATE; |
| break; |
| } |
| |
| addStateEvent(DebugEvent.CHANGE, DebugEvent.STATE, |
| new StateDelta(oldState, this.state) ); |
| |
| if (this.stateRequest == PAUSED_STATE && oldState == PAUSED_STATE) { |
| this.stateRequest= PROCESSING_STATE; |
| addStateEvent(DebugEvent.MODEL_SPECIFIC, STATE_REQUEST, |
| new StateDelta(PAUSED_STATE, PROCESSING_STATE) ); |
| } |
| else if (this.stateRequest == TERMINATED_STATE && this.state == TERMINATED_STATE) { |
| this.stateRequest= PROCESSING_STATE; |
| addStateEvent(DebugEvent.MODEL_SPECIFIC, STATE_REQUEST, |
| new StateDelta(TERMINATED_STATE, PROCESSING_STATE) ); |
| } |
| } |
| |
| boolean internal_isPauseRequested() { |
| return (this.stateRequest == PAUSED_STATE); |
| } |
| |
| |
| void internal_dispose() { |
| checkIOCache(); |
| |
| final byte oldState= this.state; |
| if (oldState != TERMINATED_STATE) { |
| this.state= TERMINATED_STATE; |
| addStateEvent(DebugEvent.CHANGE, DebugEvent.STATE, |
| new StateDelta(oldState, TERMINATED_STATE) ); |
| } |
| |
| final ImList<ToolRunnable> finalRunnables; |
| { final ToolRunnable[] runnables= new @NonNull ToolRunnable[ |
| internal_getCompleteSize() ]; |
| int i= 0; |
| for (int iSection= this.sectionStack.size() - 1; iSection >= 1; iSection--) { |
| final SubQueue removedSection= this.sectionStack.remove(iSection); |
| i= removedSection.getAll(runnables, i); |
| removedSection.dispose(); |
| } |
| { // iSection == 0 |
| i= this.topLevelSection.getAll(runnables, i); |
| this.topLevelSection.dispose(); |
| } |
| this.currentSection= this.topLevelSection; |
| this.currentSection.next= null; |
| |
| finalRunnables= ImCollections.newList(runnables); |
| } |
| for (final ToolRunnable runnable : finalRunnables) { |
| runnable.changed(ToolRunnable.BEING_ABANDONED, this.process); |
| } |
| addTerminateEvent(new TaskDelta(ToolRunnable.BEING_ABANDONED, -1, finalRunnables)); |
| |
| if (!this.hotList.isEmpty()){ |
| final ImList<ToolRunnable> leftRunnable= ImCollections.toList(this.hotList); |
| this.hotList.clear(); |
| |
| for (final ToolRunnable runnable : leftRunnable) { |
| runnable.changed(ToolRunnable.BEING_ABANDONED, this.process); |
| } |
| } |
| if (!this.onIdleList.isEmpty()){ |
| final ImList<RankedItem> leftRunnable= ImCollections.toList(this.onIdleList); |
| this.onIdleList.clear(); |
| |
| for (final RankedItem item : leftRunnable) { |
| item.runnable.changed(ToolRunnable.BEING_ABANDONED, this.process); |
| } |
| } |
| |
| internal_fireEvents(); |
| } |
| |
| |
| private void checkIOCache() { |
| if (this.singleIOCache != null) { |
| final int idx= this.currentSection.getAppendIdx(); |
| this.currentSection.append(this.singleIOCache.get(0)); |
| addContentChangeEvent(new TaskDelta(ToolRunnable.ADDING_TO, idx, this.singleIOCache)); |
| this.singleIOCache= null; |
| } |
| } |
| |
| |
| private void addContentChangeEvent(final TaskDelta delta) { |
| final DebugEvent event= new DebugEvent(this, DebugEvent.CHANGE, DebugEvent.CONTENT); |
| event.setData(delta); |
| this.eventList.add(event); |
| } |
| |
| private void addStateEvent(final int kind, final int detail, final StateDelta delta) { |
| final DebugEvent event= new DebugEvent(this, kind, detail); |
| event.setData(delta); |
| this.eventList.add(event); |
| } |
| |
| private void addTerminateEvent(final TaskDelta delta) { |
| final DebugEvent event= new DebugEvent(this, DebugEvent.TERMINATE, DebugEvent.UNSPECIFIED); |
| event.setData(delta); |
| this.eventList.add(event); |
| } |
| |
| List<DebugEvent> internal_getEventList() { |
| return this.eventList; |
| } |
| |
| void internal_fireEvents() { |
| if (this.eventList.isEmpty()) { |
| return; |
| } |
| final DebugPlugin manager= DebugPlugin.getDefault(); |
| if (manager != null) { |
| manager.fireDebugEventSet(this.eventList.toArray(new DebugEvent[this.eventList.size()])); |
| } |
| this.eventList.clear(); |
| } |
| |
| } |