| /* |
| * 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; |
| } |
| } |