| /* |
| * 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 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 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 void appendVariableValue(StringBuilder sb, Value val, final int isolateId) { appendVariableValue(sb,val,"", isolateId); } //$NON-NLS-1$ |
| |
| public 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(escapeIfIde(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(escapeIfIde(String.valueOf(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(escapeIfIde(String.valueOf(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(escapeIfIde(String.valueOf(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$ |
| } |
| |
| private String escapeIfIde(String s) |
| { |
| return m_cli != null && m_cli.isIde() ? escape(s) : s; |
| } |
| |
| public static String escape(final String str) { |
| final StringBuilder buffer = new StringBuilder(); |
| |
| for (int idx = 0; idx < str.length(); idx++) { |
| char ch = str.charAt(idx); |
| switch (ch) { |
| case '\b': |
| buffer.append("\\b"); |
| break; |
| |
| case '\t': |
| buffer.append("\\t"); |
| break; |
| |
| case '\n': |
| buffer.append("\\n"); |
| break; |
| |
| case '\f': |
| buffer.append("\\f"); |
| break; |
| |
| case '\r': |
| buffer.append("\\r"); |
| break; |
| |
| case '\\': |
| buffer.append("\\\\"); |
| break; |
| |
| default: |
| if (Character.isISOControl(ch)) { |
| String hexCode = Integer.toHexString(ch).toUpperCase(); |
| buffer.append("\\u"); |
| int paddingCount = 4 - hexCode.length(); |
| while (paddingCount-- > 0) { |
| buffer.append(0); |
| } |
| buffer.append(hexCode); |
| } else { |
| buffer.append(ch); |
| } |
| } |
| } |
| return buffer.toString(); |
| } |
| } |