blob: 4c6291a50930cd7acba6c22732d2ba8b80475a86 [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.io.IOException;
import java.io.StringReader;
import java.text.ParseException;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.Vector;
import flash.localization.LocalizationManager;
import flash.tools.debugger.Bootstrap;
import flash.tools.debugger.NoResponseException;
import flash.tools.debugger.NotConnectedException;
import flash.tools.debugger.NotSuspendedException;
import flash.tools.debugger.PlayerDebugException;
import flash.tools.debugger.Session;
import flash.tools.debugger.Value;
import flash.tools.debugger.ValueAttribute;
import flash.tools.debugger.Variable;
import flash.tools.debugger.VariableAttribute;
import flash.tools.debugger.VariableType;
import flash.tools.debugger.concrete.DValue;
import flash.tools.debugger.expression.ASTBuilder;
import flash.tools.debugger.expression.IASTBuilder;
import flash.tools.debugger.expression.NoSuchVariableException;
import flash.tools.debugger.expression.PlayerFaultException;
import flash.tools.debugger.expression.ValueExp;
public class ExpressionCache
{
Session m_session;
IASTBuilder m_builder;
Vector<Object> m_expressions;
IntProperties m_props;
DebugCLI m_cli;
/**
* Returned by evaluate().
*/
public static class EvaluationResult
{
/**
* The value to which the expression evaluated.
*/
public Object value;
/**
* The context that was used to evaluate the expression. Sometimes used
* to convert the <code>value</code> field to a <code>Value</code>
* with <code>context.toValue()</code>.
*/
public ExpressionContext context;
}
/**
* We can get at files by name or module id, eventually we will put functions in here too
*/
public ExpressionCache(DebugCLI cli)
{
m_builder = new ASTBuilder(true); // allow fdb's "*x" and "x." indirection operators
m_expressions = new Vector<Object>();
m_props = new IntProperties();
m_cli = cli;
}
public void clear() { m_expressions.clear(); }
public void unbind() { m_session = null; }
public int size() { return m_expressions.size(); }
public Object at(int i) { return m_expressions.elementAt(i); }
void setSession(Session s) { m_session = s; }
public Session getSession() { return m_session; }
public String getPackageName(int id) { return m_cli.module2ClassName(id); }
public void bind(Session s)
{
setSession(s);
// propagates our properties to the session / non-critical if fails
try { ((flash.tools.debugger.concrete.PlayerSession)s).setPreferences(m_props.map()); } catch(Exception e) {}
}
public EvaluationResult evaluate(ValueExp e, int isolateId) throws NumberFormatException, NoSuchVariableException, PlayerFaultException, PlayerDebugException
{
EvaluationResult result = new EvaluationResult();
result.context = new ExpressionContext(this);
result.context.setIsolateId(isolateId);
result.value = e.evaluate(result.context);
return result;
}
public ValueExp parse(String s) throws IOException, ParseException
{
return m_builder.parse(new StringReader(s));
}
public int add(Object e)
{
int at = m_expressions.size();
m_expressions.add(e);
return at+1;
}
//
// Interface for accessing previous expression values and also the properties
//
public boolean propertyEnabled(String which)
{
boolean enabled = false;
try
{
Number number = (Number) get(which);
if (number != null)
enabled = (number.intValue() != 0);
}
catch (Exception e)
{
// nothing; leave 'enabled' as false
}
return enabled;
}
// this goes in properties
public void put(String s, int value) { m_props.put(s, value); setSessionProperty(s, value); }
public Set<String> keySet() { return m_props.keySet(); }
/**
* Allow the session to receive property updates
*/
void setSessionProperty(String s, int value)
{
Session sess = getSession();
if (sess != null)
sess.setPreference(s, value);
Bootstrap.sessionManager().setPreference(s, value);
}
/**
* We are able to fetch properties or expressions (i.e previous expression)
* using this single call, despite the fact that each of these types of
* results lie in different data structures m_expressions and m_props.
* This allows us to easily perform expression evaluation without
* need or concern over which 'type' of $ reference we are dealing with
*/
public Object get(String s) throws NumberFormatException, ArrayIndexOutOfBoundsException, NoSuchElementException
{
Object exp = null;
// should be of form '$n' where n is a number 0..size()
if (s.charAt(0) != '$')
throw new NoSuchElementException(s);
String num = s.substring(1);
if (num == null || num.length() == 0)
exp = at(size()-1);
else if (num.equals("$")) //$NON-NLS-1$
exp = at(size()-2);
else
{
try
{
int index = Integer.parseInt(num);
exp = at(index-1);
}
catch(NumberFormatException nfe)
{
// must be in the property list
exp = m_props.getInteger(s);
}
}
return exp;
}
//
// Statics for formatting stuff
//
/**
* Formatting function for variable
*/
public static void appendVariable(StringBuilder sb, Variable v, int isolateId)
{
//sb.append('\'');
String name = v.getName();
sb.append(name);
//sb.append('\'');
sb.append(" = "); //$NON-NLS-1$
appendVariableValue(sb, v.getValue(), name, isolateId);
//appendVariableAttributes(sb, v);
}
/**
* Given any arbitrary constant value, such as a Double, a String, etc.,
* format its value appropriately. For example, strings will be quoted.
*
* @param sb
* a StringBuilder to which the formatted value will be appended.
* @param o
* the value to format.
*/
public static void appendVariableValue(StringBuilder sb, final Object o, final int isolateId)
{
Value v;
if (o instanceof Value) {
v = (Value) o;
} else {
v = new Value() {
public int getAttributes() {
return 0;
}
public String[] getClassHierarchy(boolean allLevels) {
return new String[0];
}
public String getClassName() {
return ""; //$NON-NLS-1$
}
public long getId() {
return UNKNOWN_ID;
}
public int getMemberCount(Session s) throws NotSuspendedException,
NoResponseException, NotConnectedException {
return 0;
}
public Variable getMemberNamed(Session s, String name)
throws NotSuspendedException, NoResponseException,
NotConnectedException {
return null;
}
public Variable[] getMembers(Session s)
throws NotSuspendedException, NoResponseException,
NotConnectedException {
return new Variable[0];
}
public int getType() {
if (o instanceof Number)
return VariableType.NUMBER;
else if (o instanceof Boolean)
return VariableType.BOOLEAN;
else if (o instanceof String)
return VariableType.STRING;
else if (o == Value.UNDEFINED)
return VariableType.UNDEFINED;
else if (o == null)
return VariableType.NULL;
assert false;
return VariableType.UNKNOWN;
}
public String getTypeName() {
return ""; //$NON-NLS-1$
}
public Object getValueAsObject() {
return o;
}
public String getValueAsString() {
return DValue.getValueAsString(o);
}
public boolean isAttributeSet(int variableAttribute) {
return false;
}
public Variable[] getPrivateInheritedMembers() {
return new Variable[0];
}
public Variable[] getPrivateInheritedMemberNamed(String name) {
return new Variable[0];
}
public int getIsolateId() {
return isolateId;
}
};
}
appendVariableValue(sb, v, isolateId);
}
public static void appendVariableValue(StringBuilder sb, Value val, final int isolateId) { appendVariableValue(sb,val,"", isolateId); } //$NON-NLS-1$
public static void appendVariableValue(StringBuilder sb, Value val, String variableName, final int isolateId)
{
int type = val.getType();
String typeName = val.getTypeName();
String className = val.getClassName();
// if no string or empty then typeName is blank
if (typeName != null && typeName.length() == 0)
typeName = null;
switch (type)
{
case VariableType.NUMBER:
{
double value = ((Number)val.getValueAsObject()).doubleValue();
long longValue = (long) value;
// The value is stored as a double; however, in practice most values are
// actually integers. Check to see if this is the case, and if it is,
// then display it:
// - without a fraction, and
// - with its hex equivalent in parentheses.
// Note, we use 'long' instead of 'int', in order to deal with the
// ActionScript type 'uint'.
if (longValue == value)
{
sb.append(longValue);
sb.append(" (0x"); //$NON-NLS-1$
sb.append(Long.toHexString(longValue));
sb.append(")"); //$NON-NLS-1$
}
else
{
sb.append(value);
}
break;
}
case VariableType.BOOLEAN:
{
Boolean b = (Boolean)val.getValueAsObject();
if (b.booleanValue())
sb.append("true"); //$NON-NLS-1$
else
sb.append("false"); //$NON-NLS-1$
break;
}
case VariableType.STRING:
{
// Exceptions are displayed in angle brackets, e.g.
// foo = <Text of exception here>
// Strings are displayed quoted:
// foo = "Value of string here"
//
// Note that quotation marks within the string are not escaped. This
// is sort of weird, but it's what we want to do, at least for now;
// the debugger's output is intended to be human-readable, not
// machine-readable, and it's easier for a person to read the string
// if there is no escaping of quotation marks.
//
// As a small step in the direction of avoiding that weirdness, if
// the string contains double-quotes but no single-quotes, we will
// quote it in single quotes.
String s = val.getValueAsString();
char start, end;
if (val.isAttributeSet(ValueAttribute.IS_EXCEPTION))
{
start = '<';
end = '>';
}
else if (s.indexOf('"') != -1 && s.indexOf('\'') == -1)
{
start = end = '\'';
}
else
{
start = end = '"';
}
sb.append(start);
sb.append(s);
sb.append(end);
break;
}
case VariableType.OBJECT:
{
sb.append("["); //$NON-NLS-1$
sb.append(className);
// Normally, we include the object id after the class name.
// However, when running fdbunit, don't show object IDs, so that
// results can reproduce consistently from one run to the next.
if (System.getProperty("fdbunit") == null) //$NON-NLS-1$
{
sb.append(" "); //$NON-NLS-1$
sb.append(val.getValueAsObject()); // object id
}
if (typeName != null && !typeName.equals(className))
{
sb.append(", class='"); //$NON-NLS-1$
// Often the typename is of the form 'classname@hexaddress',
// but the hex address is the same as the object id which
// is returned by getValue() -- we don't want to display it
// here.
int at = typeName.indexOf('@');
if (at != -1)
typeName = typeName.substring(0, at);
sb.append(typeName);
sb.append('\'');
}
sb.append(']');
break;
}
case VariableType.FUNCTION:
{
// here we have a special case for getters/setters which
// look like functions to us, except the attribute is set.
sb.append('[');
if (val.isAttributeSet(VariableAttribute.HAS_GETTER))
sb.append(getLocalizationManager().getLocalizedTextString("getterFunction")); //$NON-NLS-1$
else if (val.isAttributeSet(VariableAttribute.HAS_SETTER))
sb.append(getLocalizationManager().getLocalizedTextString("setterFunction")); //$NON-NLS-1$
else
sb.append(getLocalizationManager().getLocalizedTextString("function")); //$NON-NLS-1$
sb.append(' ');
sb.append(val.getValueAsObject());
if (typeName != null && !typeName.equals(variableName))
{
sb.append(", name='"); //$NON-NLS-1$
sb.append(typeName);
sb.append('\'');
}
sb.append(']');
break;
}
case VariableType.MOVIECLIP:
{
sb.append("["); //$NON-NLS-1$
sb.append(className);
sb.append(" "); //$NON-NLS-1$
sb.append(val.getValueAsObject());
if (typeName != null && !typeName.equals(className))
{
sb.append(", named='"); //$NON-NLS-1$
sb.append(typeName);
sb.append('\'');
}
sb.append(']');
break;
}
case VariableType.NULL:
{
sb.append("null"); //$NON-NLS-1$
break;
}
case VariableType.UNDEFINED:
{
sb.append("undefined"); //$NON-NLS-1$
break;
}
case VariableType.UNKNOWN:
{
sb.append(getLocalizationManager().getLocalizedTextString("unknownVariableType")); //$NON-NLS-1$
break;
}
}
}
private static LocalizationManager getLocalizationManager()
{
return DebugCLI.getLocalizationManager();
}
public static void appendVariableAttributes(StringBuilder sb, Variable v)
{
if (v.getAttributes() == 0)
return;
sb.append(" "); //$NON-NLS-1$
if (v.isAttributeSet(VariableAttribute.DONT_ENUMERATE))
sb.append(", " + getLocalizationManager().getLocalizedTextString("variableAttribute_dontEnumerate")); //$NON-NLS-1$ //$NON-NLS-2$
if (v.isAttributeSet(VariableAttribute.READ_ONLY))
sb.append(", " + getLocalizationManager().getLocalizedTextString("variableAttribute_readOnly")); //$NON-NLS-1$ //$NON-NLS-2$
if (v.isAttributeSet(VariableAttribute.IS_LOCAL))
sb.append(", " + getLocalizationManager().getLocalizedTextString("variableAttribute_localVariable")); //$NON-NLS-1$ //$NON-NLS-2$
if (v.isAttributeSet(VariableAttribute.IS_ARGUMENT))
sb.append(", " + getLocalizationManager().getLocalizedTextString("variableAttribute_functionArgument")); //$NON-NLS-1$ //$NON-NLS-2$
if (v.isAttributeSet(VariableAttribute.HAS_GETTER))
sb.append(", " + getLocalizationManager().getLocalizedTextString("variableAttribute_getterFunction")); //$NON-NLS-1$ //$NON-NLS-2$
if (v.isAttributeSet(VariableAttribute.HAS_SETTER))
sb.append(", " + getLocalizationManager().getLocalizedTextString("variableAttribute_setterFunction")); //$NON-NLS-1$ //$NON-NLS-2$
if (v.isAttributeSet(VariableAttribute.IS_DYNAMIC))
sb.append(", dynamic"); //$NON-NLS-1$
if (v.isAttributeSet(VariableAttribute.IS_STATIC))
sb.append(", static"); //$NON-NLS-1$
if (v.isAttributeSet(VariableAttribute.IS_CONST))
sb.append(", const"); //$NON-NLS-1$
if (v.isAttributeSet(VariableAttribute.PRIVATE_SCOPE))
sb.append(", private"); //$NON-NLS-1$
if (v.isAttributeSet(VariableAttribute.PUBLIC_SCOPE))
sb.append(", public"); //$NON-NLS-1$
if (v.isAttributeSet(VariableAttribute.PROTECTED_SCOPE))
sb.append(", protected"); //$NON-NLS-1$
if (v.isAttributeSet(VariableAttribute.INTERNAL_SCOPE))
sb.append(", internal"); //$NON-NLS-1$
if (v.isAttributeSet(VariableAttribute.NAMESPACE_SCOPE))
sb.append(", " + getLocalizationManager().getLocalizedTextString("variableAttribute_hasNamespace")); //$NON-NLS-1$ //$NON-NLS-2$
}
}