blob: 75eabbab637ac43ebcad566f56457555d9511526 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package flex.tools.debugger.cli;
import java.util.StringTokenizer;
import java.util.Vector;
import flash.tools.debugger.Isolate;
import flash.tools.debugger.PlayerDebugException;
import flash.tools.debugger.Session;
import flash.tools.debugger.SessionManager;
import flash.tools.debugger.Value;
import flash.tools.debugger.ValueAttribute;
import flash.tools.debugger.Variable;
import flash.tools.debugger.VariableType;
import flash.tools.debugger.concrete.DValue;
import flash.tools.debugger.events.ExceptionFault;
import flash.tools.debugger.events.FaultEvent;
import flash.tools.debugger.expression.Context;
import flash.tools.debugger.expression.ExpressionEvaluatorException;
import flash.tools.debugger.expression.NoSuchVariableException;
import flash.tools.debugger.expression.PlayerFaultException;
public class ExpressionContext implements Context
{
ExpressionCache m_cache;
Object m_current;
boolean m_createIfMissing; // set if we need to create a variable if it doesn't exist
Vector<String> m_namedPath;
boolean m_nameLocked;
String m_newline = System.getProperty("line.separator"); //$NON-NLS-1$
int m_isolateId;
// used when evaluating an expression
public ExpressionContext(ExpressionCache cache)
{
m_cache = cache;
m_current = null;
m_createIfMissing = false;
m_namedPath = new Vector<String>();
m_nameLocked = false;
m_isolateId = Isolate.DEFAULT_ID;
}
public void setIsolateId(int id) {
m_isolateId = id;
}
void setContext(Object o) { m_current = o; }
void pushName(String name) { if (m_nameLocked || name.length() < 1) return; m_namedPath.add(name); }
boolean setName(String name) { if (m_nameLocked) return true; m_namedPath.clear(); pushName(name); return true; }
void lockName() { m_nameLocked = true; }
public String getName()
{
int size = m_namedPath.size();
StringBuilder sb = new StringBuilder();
for(int i=0; i<size; i++)
{
String s = m_namedPath.get(i);
if (i > 0)
sb.append('.');
sb.append(s);
}
return ( sb.toString() );
}
String getCurrentPackageName()
{
String s = null;
try
{
Integer o = (Integer)m_cache.get(DebugCLI.LIST_MODULE);
s = m_cache.getPackageName(o.intValue());
}
catch(NullPointerException npe)
{
}
catch(ClassCastException cce)
{
}
return s;
}
//
//
// Start of Context API implementation
//
//
public void createPseudoVariables(boolean oui) { m_createIfMissing = oui; }
// create a new context object by combining the current one and o
public Context createContext(Object o)
{
ExpressionContext c = new ExpressionContext(m_cache);
c.setContext(o);
c.createPseudoVariables(m_createIfMissing);
c.m_namedPath.addAll(m_namedPath);
c.setIsolateId(m_isolateId);
return c;
}
// assign the object o, the value v
public void assign(Object o, Value v) throws NoSuchVariableException, PlayerFaultException
{
try
{
// first see if it is an internal property (avoids player calls)
InternalProperty prop = resolveToInternalProperty(o);
// we expect that o is a variable that can be resolved or is a specially marked internal variable
if (prop != null)
{
assignInternal(prop, v);
}
else
{
boolean wasCreateIfMissing = m_createIfMissing;
createPseudoVariables(true);
Variable var = null;
try {
var = resolveToVariable(o);
} finally {
createPseudoVariables(wasCreateIfMissing);
}
if (var == null)
throw new NoSuchVariableException((var == null) ? m_current : var.getName());
// set the value, for the case of a variable that does not exist it will not have a type
// so we try to glean one from v.
FaultEvent faultEvent = var.setValue(getSession(), v.getType(), v.getValueAsString());
if (faultEvent != null)
throw new PlayerFaultException(faultEvent);
}
}
catch(PlayerDebugException pde)
{
throw new ExpressionEvaluatorException(pde);
}
}
/**
* The Context interface which goes out and gets values from the session
* Expressions use this interface as a means of evaluation.
*
* We also use this to create a reference to internal variables.
*/
public Object lookup(Object o) throws NoSuchVariableException, PlayerFaultException
{
Object result = null;
try
{
// first see if it is an internal property (avoids player calls)
if ( (result = resolveToInternalProperty(o)) != null)
;
// attempt to resolve to a player variable
else if ( (result = resolveToVariable(o)) != null)
;
// or value
else if ( (result = resolveToValue(o)) != null)
;
else
throw new NoSuchVariableException(o);
// take on the path to the variable; so 'what' command prints something nice
if ((result != null) && result instanceof VariableFacade)
{
((VariableFacade)result).setPath(getName());
}
// if the attempt to get the variable's value threw an exception inside the
// player (most likely because the variable is actually a getter, and the
// getter threw something), then throw something here
Value resultValue = null;
if (result instanceof Variable)
{
if (result instanceof VariableFacade && ((VariableFacade)result).getVariable() == null)
resultValue = null;
else
resultValue = ((Variable)result).getValue();
}
else if (result instanceof Value)
{
resultValue = (Value) result;
}
if (resultValue != null)
{
if (resultValue.isAttributeSet(ValueAttribute.IS_EXCEPTION))
{
String value = resultValue.getValueAsString();
throw new PlayerFaultException(new ExceptionFault(value, false, resultValue, resultValue.getIsolateId()));
}
}
}
catch(PlayerDebugException pde)
{
result = Value.UNDEFINED;
}
return result;
}
/* returns a string consisting of formatted member names and values */
public Object lookupMembers(Object o) throws NoSuchVariableException
{
Variable var = null;
Value val = null;
Variable[] mems = null;
try
{
var = resolveToVariable(o);
if (var != null)
val = var.getValue();
else
val = resolveToValue(o);
mems = val.getMembers(getSession());
}
catch(NullPointerException npe)
{
throw new NoSuchVariableException(o);
}
catch(PlayerDebugException pde)
{
throw new NoSuchVariableException(o); // not quite right...
}
StringBuilder sb = new StringBuilder();
if (var != null)
ExpressionCache.appendVariable(sb, var, m_isolateId);
else
ExpressionCache.appendVariableValue(sb, val, m_isolateId);
boolean attrs = m_cache.propertyEnabled(DebugCLI.DISPLAY_ATTRIBUTES);
if (attrs && var != null)
ExpressionCache.appendVariableAttributes(sb, var);
// [mmorearty] experimenting with hierarchical display of members
String[] classHierarchy = val.getClassHierarchy(false);
if (classHierarchy != null && getSession().getPreference(SessionManager.PREF_HIERARCHICAL_VARIABLES) != 0)
{
for (int c=0; c<classHierarchy.length; ++c)
{
String classname = classHierarchy[c];
sb.append(m_newline + "(Members of " + classname + ")"); //$NON-NLS-1$ //$NON-NLS-2$
for (int i=0; i<mems.length; ++i)
{
if (classname.equals(mems[i].getDefiningClass()))
{
sb.append(m_newline + " "); //$NON-NLS-1$
ExpressionCache.appendVariable(sb, mems[i], m_isolateId);
if (attrs)
ExpressionCache.appendVariableAttributes(sb, mems[i]);
}
}
}
}
else
{
for(int i=0; i<mems.length; i++)
{
sb.append(m_newline + " "); //$NON-NLS-1$
ExpressionCache.appendVariable(sb, mems[i], m_isolateId);
if (attrs)
ExpressionCache.appendVariableAttributes(sb, mems[i]);
}
}
return sb.toString();
}
//
//
// End of Context API implementation
//
//
// used to assign a value to an internal variable
private void assignInternal(InternalProperty var, Value v) throws NoSuchVariableException, NumberFormatException, PlayerDebugException
{
// otherwise set it
if (v.getType() != VariableType.NUMBER)
throw new NumberFormatException(v.getValueAsString());
long l = Long.parseLong(v.getValueAsString());
m_cache.put(var.getName(), (int)l);
}
InternalProperty resolveToInternalProperty(Object o)
{
if (o instanceof String && ((String)o).charAt(0) == '$')
{
String key = (String)o;
Object value = null;
try { value = m_cache.get(key); } catch(Exception e) {}
return new InternalProperty(key, value);
}
return null;
}
/**
* Resolve the object into a variable by various means and
* using the current context.
* @return variable, or <code>null</code>
*/
Variable resolveToVariable(Object o) throws PlayerDebugException
{
Variable v = null;
// if o is a variable already, then we're done!
if (o instanceof Variable)
return (Variable)o;
/**
* Resolve the name to something
*/
{
// not an id so try as name
String name = o.toString();
long id = nameAsId(name);
/**
* if #N was used just pick up the variable, otherwise
* we need to use the current context to resolve
* the name to a member
*/
if (id != Value.UNKNOWN_ID)
{
// TODO what here?
}
else
{
// try to resolve as a member of current context (will set context if null)
id = determineContext(name);
v = locateForNamed(id, name, true);
if (v != null)
v = new VariableFacade(v, id, m_isolateId);
else if (v == null && m_createIfMissing && name.charAt(0) != '$')
v = new VariableFacade(id, name, m_isolateId);
}
}
/* return the variable */
return v;
}
/*
* Resolve the object into a variable by various means and
* using the current context.
*/
Value resolveToValue(Object o) throws PlayerDebugException
{
Value v = null;
// if o is a variable or a value already, then we're done!
if (o instanceof Value)
return (Value)o;
else if (o instanceof Variable)
return ((Variable)o).getValue();
else if (o instanceof InternalProperty)
return DValue.forPrimitive(((InternalProperty)o).m_value, m_isolateId);
/**
* Resolve the name to something
*/
if (m_current == null)
{
// not an id so try as name
String name = o.toString();
long id = nameAsId(name);
/**
* if #N was used just pick up the variable, otherwise
* we need to use the current context to resolve
* the name to a member
*/
if (id != Value.UNKNOWN_ID)
{
v = getSession().getWorkerSession(m_isolateId).getValue((int)id);
}
else if (name.equals("undefined")) //$NON-NLS-1$
{
v = DValue.forPrimitive(Value.UNDEFINED, m_isolateId);
}
else
{
// Ask the player to find something, anything, on the scope chain
// with this name. We'll end up here, for example, when resolving
// things like MyClass, String, Number, etc.
v = getSession().getWorkerSession(m_isolateId).getGlobal(name);
}
}
/* return the value */
return v;
}
// special code for #N support. I.e. naming a variable via an ID
long nameAsId(String name)
{
long id = Value.UNKNOWN_ID;
try
{
if (name.charAt(0) == '#')
id = Long.parseLong(name.substring(1));
}
catch(Exception e)
{
id = Value.UNKNOWN_ID;
}
return id;
}
/**
* Using the given id as a parent find the member named
* name.
* @throws NoSuchVariableException if id is UNKNOWN_ID
*/
Variable memberNamed(long id, String name) throws NoSuchVariableException, PlayerDebugException
{
Variable v = null;
Value parent = getSession().getWorkerSession(m_isolateId).getValue(id);
if (parent == null)
throw new NoSuchVariableException(name);
/* got a variable now return the member if any */
v = parent.getMemberNamed(getSession(), name);
return v;
}
/**
* All the really good stuff about finding where name exists goes here!
*
* If name is not null, then it implies that we use the existing
* m_current to find a member of m_current. If m_current is null
* Then we need to probe variable context points attempting to locate
* name. When we find a match we set the m_current to this context
*
* If name is null then we simply return the current context.
*/
long determineContext(String name) throws PlayerDebugException
{
long id = Value.UNKNOWN_ID;
// have we already resolved our context...
if (m_current != null)
{
id = toValue().getId();
}
// nothing to go on, so we're done
else if (name == null)
;
// use the name and try and resolve where we are...
else
{
// Each stack frame has a root variable under (BASE_ID-depth)
// where depth is the depth of the stack.
// So we query for our current stack depth and use that
// as the context for our base computation
long baseId = Value.BASE_ID;
int depth = ((Integer)m_cache.get(DebugCLI.DISPLAY_FRAME_NUMBER)).intValue();
baseId -= depth;
// obtain data about our current state
Variable contextVar = null;
Value contextVal = null;
Value val = null;
// look for 'name' starting from local scope
if ( (val = locateParentForNamed(baseId, name, false)) != null)
;
// get the this pointer, then look for 'name' starting from that point
else if ( ( (contextVar = locateForNamed(baseId, "this", false)) != null ) && //$NON-NLS-1$
( setName("this") && (val = locateParentForNamed(contextVar.getValue().getId(), name, true)) != null ) ) //$NON-NLS-1$
;
// now try to see if 'name' exists off of _root
else if ( setName("_root") && (val = locateParentForNamed(Value.ROOT_ID, name, true)) != null ) //$NON-NLS-1$
;
// now try to see if 'name' exists off of _global
else if ( setName("_global") && (val = locateParentForNamed(Value.GLOBAL_ID, name, true)) != null ) //$NON-NLS-1$
;
// now try off of class level, if such a thing can be found
else if ( ( (contextVal = locate(Value.GLOBAL_ID, getCurrentPackageName(), false)) != null ) &&
( setName("_global."+getCurrentPackageName()) && (val = locateParentForNamed(contextVal.getId(), name, true)) != null ) ) //$NON-NLS-1$
;
// if we found it then stake this as our context!
if (val != null)
{
id = val.getId();
pushName(name);
lockName();
}
}
return id;
}
/**
* Performs a search for a member with the given name using the
* given id as the parent variable.
*
* If a match is found then, we return the parent variable of
* the member that matched. The proto chain is optionally traversed.
*
* No exceptions are thrown
*/
Value locateParentForNamed(long id, String name, boolean traverseProto) throws PlayerDebugException
{
StringBuilder sb = new StringBuilder();
Variable var = null;
Value val = null;
try
{
var = memberNamed(id, name);
// see if we need to traverse the proto chain
while (var == null && traverseProto)
{
// first attempt to get __proto__, then resolve name
Variable proto = memberNamed(id, "__proto__"); //$NON-NLS-1$
sb.append("__proto__"); //$NON-NLS-1$
if (proto == null)
traverseProto = false;
else
{
id = proto.getValue().getId();
var = memberNamed(id, name);
if (var == null)
sb.append('.');
}
}
}
catch(NoSuchVariableException nsv)
{
// don't worry about this one, it means variable with id couldn't be found
}
catch(NullPointerException npe)
{
// probably no session
}
// what we really want is the parent not the child variable
if (var != null)
{
pushName(sb.toString());
val = getSession().getWorkerSession(m_isolateId).getValue(id);
}
return val;
}
// variant of locateParentForNamed, whereby we return the child variable
Variable locateForNamed(long id, String name, boolean traverseProto) throws PlayerDebugException
{
Variable var = null;
Value v = locateParentForNamed(id, name, traverseProto);
if (v != null)
{
try
{
var = memberNamed(v.getId(), name);
}
catch(NoSuchVariableException nse)
{
v = null;
}
}
return var;
}
/**
* Locates the member via a dotted name starting at the given id.
* It will traverse any and all proto chains if necc. to find the name.
*/
Value locate(long startingId, String dottedName, boolean traverseProto) throws PlayerDebugException
{
if (dottedName == null)
return null;
// first rip apart the dottedName
StringTokenizer names = new StringTokenizer(dottedName, "."); //$NON-NLS-1$
Value val = getSession().getWorkerSession(m_isolateId).getValue(startingId);
while(names.hasMoreTokens() && val != null)
val = locateForNamed(val.getId(), names.nextToken(), traverseProto).getValue();
return val;
}
/*
* @see flash.tools.debugger.expression.Context#toValue(java.lang.Object)
*/
public Value toValue(Object o)
{
// if o is a variable or a value already, then we're done!
if (o instanceof Value)
return (Value)o;
else if (o instanceof Variable)
return ((Variable)o).getValue();
else if (o instanceof InternalProperty)
return DValue.forPrimitive(((InternalProperty)o).m_value, m_isolateId);
else
return DValue.forPrimitive(o, m_isolateId);
}
public Value toValue()
{
return toValue(m_current);
}
public Session getSession()
{
return m_cache.getSession();
}
@Override
public int getIsolateId() {
return m_isolateId;
}
}