/**
* Copyright (c) 2005-2012 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
package org.python.pydev.debug.model;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.debug.core.DebugException;
import org.python.pydev.debug.model.remote.AbstractDebuggerCommand;
import org.python.pydev.debug.model.remote.GetVariableCommand;
import org.python.pydev.debug.model.remote.ICommandResponseListener;
import org.python.pydev.shared_core.log.Log;
public class VariablesLoader implements ICommandResponseListener {
private volatile PyVariable[] currentVariables;
private volatile PyVariable[] oldVariables;
private final ContainerOfVariables parent;
private IProgressMonitor monitor;
private boolean addGlobalsVariable;
public VariablesLoader(ContainerOfVariables parent, boolean addGlobalsVariable) {
this.parent = parent;
this.addGlobalsVariable = addGlobalsVariable;
}
private AbstractDebugTarget getTarget() {
return this.parent.getTarget();
}
private IVariableLocator getLocator() {
return this.parent.getLocator();
}
public PyVariable[] fetchVariables() {
oldVariables = currentVariables;
currentVariables = null;
AbstractDebugTarget target = this.getTarget();
if (target == null) {
return new PyVariable[0];
}
GetVariableCommand variableCommand = this.parent.getVariableCommand(target);
variableCommand.setCompletionListener(this);
target.postCommand(variableCommand);
return waitForCommand();
}
private PyVariable[] waitForCommand() {
try {
// VariablesView does not deal well with children changing asynchronously.
// it causes unneeded scrolling, because view preserves selection instead
// of visibility.
// I try to minimize the occurrence here, by giving pydevd time to complete the
// task before we are forced to do asynchronous notification.
int i = 150; //up to 1.5 seconds
while (--i > 0 && currentVariables == null) {
if (this.monitor != null && this.monitor.isCanceled() == true) {
//canceled request... let's return
return new PyVariable[0];
}
Thread.sleep(10); //10 millis
}
} catch (InterruptedException e) {
Log.log(e);
}
if (currentVariables != null) {
return currentVariables;
}
return new PyVariable[0];
}
@Override
public void commandComplete(AbstractDebuggerCommand cmd) {
AbstractDebugTarget target = getTarget();
IVariableLocator locator = getLocator();
if (target == null || locator == null) {
return;
}
PyVariable[] temp = PyVariableCollection.getCommandVariables(cmd, target, locator);
if (addGlobalsVariable) {
PyVariable[] temp1 = new PyVariable[temp.length + 1];
System.arraycopy(temp, 0, temp1, 1, temp.length);
temp1[0] = new PyVariableCollection(target, "Globals", "frame.f_globals", "Global variables",
this.parent.getGlobalLocator());
temp = temp1;
}
PyVariable[] newVars = this.verifyVariablesModified(temp, oldVariables);
currentVariables = parent.setVariables(newVars);
}
/**
* Compares stack frames to check for modified variables (and mark them as modified in the new stack).
* Tries to reuse variables from the old list so that the tree state is kept on the variables view.
*
* @returns the list to be used for the frame (which uses old variables when possible if they're compatible,
* even updating them in-place as needed).
*/
private PyVariable[] verifyVariablesModified(PyVariable[] newFrameVariables, PyVariable[] oldVariables) {
if (oldVariables == null || newFrameVariables == oldVariables) {
return newFrameVariables; //All variables are new, so, no point in notifying it.
}
ArrayList<PyVariable> newVarsList = new ArrayList<>(newFrameVariables.length);
PyVariable newVariable = null;
try {
Map<String, PyVariable> map = new HashMap<String, PyVariable>();
for (PyVariable var : oldVariables) {
map.put(var.getPyDBLocation(), var);
}
Map<String, PyVariable> variablesAsMap = map;
//we have to check for each new variable
for (int i = 0; i < newFrameVariables.length; i++) {
newVariable = newFrameVariables[i];
PyVariable oldVariable = variablesAsMap.get(newVariable.getPyDBLocation());
if (oldVariable != null) {
boolean equals;
if (newVariable.getClass() != oldVariable.getClass()) {
// Changed from collection to simple var (or vice-versa).
// If it's a new variable, we don't need to force it to get new variables
// (this will happen naturally when requested).
newVariable.setModified(true);
newVarsList.add(newVariable);
} else {
// Same class: always use old variable (and set the value string accordingly)
String newValueString = newVariable.getValueString();
equals = newValueString.equals(oldVariable.getValueString());
if (!equals) {
oldVariable.copyValueString(newVariable);
}
// If it is not equal, it was modified.
oldVariable.setModified(!equals);
// Always force an existing variable collection to get new variables.
oldVariable.forceGetNewVariables();
newVarsList.add(oldVariable);
}
} else { // It didn't exist before...
newVariable.setModified(true);
newVarsList.add(newVariable);
}
}
} catch (DebugException e) {
Log.log(e);
}
return newVarsList.toArray(new PyVariable[0]);
}
}