blob: ab54d88bb6348a8fbb10a4a2669b3199cddf5f05 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2016, 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.internal.r.debug.core.eval;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IExpressionManager;
import org.eclipse.statet.jcommons.collections.CopyOnWriteList;
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.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.ToolService;
import org.eclipse.statet.ecommons.debug.core.eval.IEvaluationListener;
import org.eclipse.statet.internal.r.debug.core.Messages;
import org.eclipse.statet.internal.r.debug.core.eval.REvalExpressionTask.Key;
import org.eclipse.statet.internal.r.debug.core.model.RDebugTarget;
import org.eclipse.statet.internal.r.debug.core.model.RMainThread;
import org.eclipse.statet.internal.r.debug.core.model.RStackFrame;
import org.eclipse.statet.r.console.core.RProcess;
import org.eclipse.statet.r.core.tool.TmpUtils;
import org.eclipse.statet.r.debug.core.IRStackFrame;
import org.eclipse.statet.r.nico.AbstractRDbgController;
@NonNullByDefault
public class ExpressionManager {
private class CleanRunnable implements SystemRunnable {
private boolean scheduled;
public CleanRunnable() {
}
@Override
public String getTypeId() {
return "r/dbg/exprs/clean"; //$NON-NLS-1$
}
@Override
public String getLabel() {
return Messages.Expression_Clean_task;
}
@Override
public boolean canRunIn(final Tool tool) {
return (tool == ExpressionManager.this.thread.getTool());
}
@Override
public boolean changed(final int event, final Tool process) {
switch (event) {
case REMOVING_FROM:
case MOVING_FROM:
return false;
case BEING_ABANDONED:
// case FINISHING_: // handled in #loadContext
break;
default:
break;
}
return true;
}
@Override
public void run(final ToolService service, final ProgressMonitor m) throws StatusException {
synchronized (this) {
this.scheduled= false;
}
cleanEvalResults((AbstractRDbgController) service, m);
}
}
private final Map<REvalExpressionTask.Key, @Nullable REvaluationResult> evalResults= new HashMap<>();
private final List<@NonNull REvaluationResult> oldEvalResults= new ArrayList<>();
private final CopyOnWriteList<@NonNull REvaluationExpression> expressions= new CopyOnWriteList<>();
private final RMainThread thread;
private final CleanRunnable cleanRunnable= new CleanRunnable();
public ExpressionManager(final RMainThread thread) {
this.thread= thread;
}
public void clearCache(final int stamp, final @Nullable ProgressMonitor m) {
synchronized (this) {
if (!this.evalResults.isEmpty()) {
for (final Iterator<Entry<Key, @Nullable REvaluationResult>> iter= this.evalResults.entrySet().iterator(); iter.hasNext();) {
final Entry<Key, @Nullable REvaluationResult> entry= iter.next();
final REvaluationResult result= entry.getValue();
if (result != null) {
this.oldEvalResults.add(result);
entry.setValue(null);
}
else {
iter.remove();
}
}
}
}
int cleanCounter= 0;
for (final REvaluationResult result : this.oldEvalResults) {
if (result.isLocked()) {
if (stamp != 0) {
result.reset(stamp, m);
}
}
else {
cleanCounter++;
}
}
if (cleanCounter > 0) {
scheduleClean();
}
}
public void evaluate(final String expressionText, final IRStackFrame stackFrame,
final boolean forceReevaluate, final IEvaluationListener listener) {
if (!checkExpression(expressionText, listener)) {
return;
}
final REvalExpressionTask task= new REvalExpressionTask(expressionText,
(@NonNull RStackFrame) stackFrame );
REvaluationResult result;
final REvaluationResult prev;
synchronized (this) {
if (forceReevaluate) {
result= null;
this.evalResults.put(task.getKey(), null);
}
else {
result= this.evalResults.get(task.getKey());
if (result != null) {
result.lock();
}
}
}
if (result == null) {
final int stamp= this.thread.checkStackFrame(stackFrame);
if (stamp != 0) {
final RProcess tool= this.thread.getTool();
final EvalExpressionRunnable runnable= new EvalExpressionRunnable(task, stamp,
listener );
synchronized (runnable) {
if (tool.getQueue().add(runnable).getSeverity() == Status.OK) {
// async
return;
}
}
}
result= new REvaluationResult(expressionText, this.thread);
}
listener.evaluationFinished(result);
}
private boolean checkExpression(final String rExpression, final IEvaluationListener listener) {
final RDebugTarget debugTarget= this.thread.getDebugTarget();
final ExpressionValidator expressionValidator= debugTarget.getExpressionValidator();
final String errorMessage;
synchronized (expressionValidator) {
errorMessage= expressionValidator.checkExpression(rExpression);
}
if (errorMessage != null) {
listener.evaluationFinished(new REvaluationResult(rExpression, this.thread,
IStatus.ERROR, errorMessage ));
return false;
}
return true;
}
public void setEvalResult(final REvalExpressionTask.Key evalKey,
final REvaluationResult result) {
final REvaluationResult prev;
synchronized (this) {
prev= this.evalResults.put(evalKey, result);
}
if (prev != null) {
prev.free();
}
}
public @Nullable REvaluationResult getEvalResult(final REvalExpressionTask.Key evalKey) {
synchronized (this) {
final REvaluationResult result= this.evalResults.get(evalKey);
if (result != null) {
result.lock();
}
return result;
}
}
public void scheduleClean() {
synchronized (this.cleanRunnable) {
if (this.cleanRunnable.scheduled) {
return;
}
this.thread.getTool().getQueue().addHot(this.cleanRunnable);
this.cleanRunnable.scheduled= true;
}
}
private void cleanEvalResults(
final AbstractRDbgController r, final ProgressMonitor m) {
for (final Iterator<REvaluationResult> iter= this.oldEvalResults.iterator(); iter.hasNext(); ) {
final REvaluationResult result= iter.next();
if (result.isLocked()) {
continue;
}
iter.remove();
final TmpUtils.Item tmpId= result.getTmpItem();
if (tmpId != null) {
tmpId.disposeChecked(m);
}
}
}
public void register(final REvaluationExpression expression) {
this.expressions.add(expression);
}
public void unregister(final REvaluationExpression expression) {
this.expressions.remove(expression);
}
public void updateExpressions(final List<@NonNull DebugEvent> eventCollection) {
final ImList<@NonNull REvaluationExpression> list= this.expressions.toList();
for (final REvaluationExpression expression : list) {
eventCollection.add(new DebugEvent(expression, DebugEvent.CHANGE, DebugEvent.CONTENT));
}
}
public void cleanExpressions(final List<@NonNull DebugEvent> eventCollection) {
final ImList<@NonNull REvaluationExpression> list= this.expressions.clearToList();
final IExpressionManager manager= DebugPlugin.getDefault().getExpressionManager();
for (final REvaluationExpression expression : list) {
manager.removeExpression(expression);
}
}
}