http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/07f5a7de/debugger/src/main/java/flex/tools/debugger/cli/DisplayAction.java ---------------------------------------------------------------------- diff --git a/debugger/src/main/java/flex/tools/debugger/cli/DisplayAction.java b/debugger/src/main/java/flex/tools/debugger/cli/DisplayAction.java new file mode 100644 index 0000000..d8612d5 --- /dev/null +++ b/debugger/src/main/java/flex/tools/debugger/cli/DisplayAction.java @@ -0,0 +1,62 @@ +/* + * 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 flash.tools.debugger.expression.ValueExp; + +/** + * An object that relates a CLI debugger 'display' command + * with the contents of the display + */ +public class DisplayAction +{ + private static int s_uniqueIdentifier = 1; + + boolean m_enabled; + int m_id; + ValueExp m_expression; + String m_content; + int m_isolateId; + + public DisplayAction(ValueExp expr, String content, int isolateId) + { + init(); + m_expression = expr; + m_content = content; + m_isolateId = isolateId; + } + + void init() + { + m_enabled = true; + m_id = s_uniqueIdentifier++; + } + + /* getters */ + public String getContent() { return m_content; } + public int getId() { return m_id; } + + public int getIsolateId() { + return m_isolateId; + } + public boolean isEnabled() { return m_enabled; } + public ValueExp getExpression() { return m_expression; } + + /* setters */ + public void setEnabled(boolean enable) { m_enabled = enable; } +}
http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/07f5a7de/debugger/src/main/java/flex/tools/debugger/cli/ExpressionCache.java ---------------------------------------------------------------------- diff --git a/debugger/src/main/java/flex/tools/debugger/cli/ExpressionCache.java b/debugger/src/main/java/flex/tools/debugger/cli/ExpressionCache.java new file mode 100644 index 0000000..95698d5 --- /dev/null +++ b/debugger/src/main/java/flex/tools/debugger/cli/ExpressionCache.java @@ -0,0 +1,596 @@ +/* + * 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(); + } +} http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/07f5a7de/debugger/src/main/java/flex/tools/debugger/cli/ExpressionContext.java ---------------------------------------------------------------------- diff --git a/debugger/src/main/java/flex/tools/debugger/cli/ExpressionContext.java b/debugger/src/main/java/flex/tools/debugger/cli/ExpressionContext.java new file mode 100644 index 0000000..2bed5ee --- /dev/null +++ b/debugger/src/main/java/flex/tools/debugger/cli/ExpressionContext.java @@ -0,0 +1,653 @@ +/* + * 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) + m_cache.appendVariable(sb, var, m_isolateId); + else + m_cache.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$ + m_cache.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$ + m_cache.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; + } +} http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/07f5a7de/debugger/src/main/java/flex/tools/debugger/cli/Extensions.java ---------------------------------------------------------------------- diff --git a/debugger/src/main/java/flex/tools/debugger/cli/Extensions.java b/debugger/src/main/java/flex/tools/debugger/cli/Extensions.java new file mode 100644 index 0000000..0222290 --- /dev/null +++ b/debugger/src/main/java/flex/tools/debugger/cli/Extensions.java @@ -0,0 +1,547 @@ +/* + * 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.PrintWriter; +import java.text.ParseException; +import java.util.HashMap; +import java.util.Map; + +import flash.localization.LocalizationManager; +import flash.swf.tools.Disassembler; +import flash.swf.types.ActionList; +import flash.tools.ActionLocation; +import flash.tools.debugger.Bootstrap; +import flash.tools.debugger.NotConnectedException; +import flash.tools.debugger.PlayerDebugException; +import flash.tools.debugger.Session; +import flash.tools.debugger.SourceFile; +import flash.tools.debugger.SuspendReason; +import flash.tools.debugger.SuspendedException; +import flash.tools.debugger.SwfInfo; +import flash.tools.debugger.Value; +import flash.tools.debugger.concrete.DMessage; +import flash.tools.debugger.concrete.DMessageCounter; +import flash.tools.debugger.concrete.DModule; +import flash.tools.debugger.concrete.DSuspendInfo; +import flash.tools.debugger.concrete.DSwfInfo; +import flash.tools.debugger.concrete.PlayerSession; +import flash.tools.debugger.concrete.PlayerSessionManager; +import flash.util.FieldFormat; + +/** + * Extensions class is a singleton that contains + * every cli method that does not conform to the + * API. Thus we can easily remove these features + * from the cli if the implementation does not + * support these calls. + */ +public class Extensions +{ + public final static String m_newline = System.getProperty("line.separator"); //$NON-NLS-1$ + + public static void doShowStats(DebugCLI cli) throws IllegalStateException + { + /* we do some magic casting */ + Session session = cli.getSession(); + StringBuilder sb = new StringBuilder(); + try + { + PlayerSession p = (PlayerSession)session; + DMessageCounter cnt = p.getMessageCounter(); + + sb.append(getLocalizationManager().getLocalizedTextString("key16")); //$NON-NLS-1$ + sb.append(m_newline); + for(int i=0; i<=DMessage.InSIZE; i++) + { + long amt = cnt.getInCount(i); + if (amt > 0) + { + sb.append('\n'); + sb.append(DMessage.inTypeName(i)); + sb.append(" = "); //$NON-NLS-1$ + sb.append(amt); + } + } + + sb.append("\n\n"); //$NON-NLS-1$ + sb.append(getLocalizationManager().getLocalizedTextString("key17")); //$NON-NLS-1$ + sb.append("\n"); //$NON-NLS-1$ + for(int i=0; i<=DMessage.OutSIZE; i++) + { + long amt = cnt.getOutCount(i); + if (amt > 0) + { + sb.append('\n'); + sb.append(DMessage.outTypeName(i)); + sb.append(" = "); //$NON-NLS-1$ + sb.append(amt); + } + } + + sb.append('\n'); + cli.out( sb.toString() ); + } + catch(NullPointerException e) + { + throw new IllegalStateException(); + } + } + + public static void doShowFuncs(DebugCLI cli) + { + StringBuilder sb = new StringBuilder(); + + String arg = null; + FileInfoCache fileInfo = cli.getFileCache(); + + // we take an optional single arg which specifies a module + try + { + if (cli.hasMoreTokens()) + { + arg = cli.nextToken(); + int id = arg.equals(".") ? cli.propertyGet(DebugCLI.LIST_MODULE) : cli.parseFileArg(cli.getActiveIsolateId(), -1, arg); //$NON + + DModule m = (DModule)fileInfo.getFile(id, cli.getActiveIsolateId()); + m.lineMapping(sb); + } + else + { + SourceFile[] ar = fileInfo.getFileList(); + if (ar == null) + cli.err(getLocalizationManager().getLocalizedTextString("key18")); //$NON-NLS-1$ + else + { + for (int i = 0; ar != null && i < ar.length; i++) + { + DModule m = (DModule)ar[i]; + m.lineMapping(sb); + } + } + } + + cli.out(sb.toString()); + } + catch(NullPointerException npe) + { + cli.err(getLocalizationManager().getLocalizedTextString("key19")); //$NON-NLS-1$ + } + catch(ParseException pe) + { + cli.err(pe.getMessage()); + } + catch(AmbiguousException ae) + { + cli.err(ae.getMessage()); + } + catch(NoMatchException nme) + { + cli.err(nme.getMessage()); + } + } + + /** + * Dump the content of internal variables + */ + public static void doShowProperties(DebugCLI cli) + { + StringBuilder sb = new StringBuilder(); + + Session session = cli.getSession(); + for (String key: cli.propertyKeys()) + { + int value = cli.propertyGet(key); + sb.append(key); + sb.append(" = "); //$NON-NLS-1$ + sb.append(value); + sb.append('\n'); + } + + // session manager + { + PlayerSessionManager mgr = (PlayerSessionManager)Bootstrap.sessionManager(); + sb.append(getLocalizationManager().getLocalizedTextString("key21")); //$NON-NLS-1$ + sb.append('\n'); + for (String key: mgr.keySet()) + { + Object value = mgr.getPreferenceAsObject(key); + sb.append(key); + sb.append(" = "); //$NON-NLS-1$ + sb.append(value); + sb.append('\n'); + } + } + + if (session != null) + { + PlayerSession psession = (PlayerSession)session; + sb.append(getLocalizationManager().getLocalizedTextString("key22")); //$NON-NLS-1$ + sb.append('\n'); + for (String key: psession.keySet()) + { + Object value = psession.getPreferenceAsObject(key); + sb.append(key); + sb.append(" = "); //$NON-NLS-1$ + sb.append(value); + sb.append('\n'); + } + } + + cli.out( sb.toString() ); + } + + /** + * Dump the break reason and offset + */ + public static void doShowBreak(DebugCLI cli) throws NotConnectedException + { + int isolateId = cli.getActiveIsolateId(); + cli.waitTilHalted(isolateId); + try + { + Session session = cli.getSession(); + StringBuilder sb = new StringBuilder(); + if (session.getWorkerSession(isolateId).isSuspended()) + { + sb.append(getLocalizationManager().getLocalizedTextString("stopped")); //$NON-NLS-1$ + sb.append(' '); + appendBreakInfo(cli, sb, true, isolateId); + } + else + sb.append(getLocalizationManager().getLocalizedTextString("key24")); //$NON-NLS-1$ + + cli.out( sb.toString() ); + } + catch(NullPointerException npe) + { + cli.err(getLocalizationManager().getLocalizedTextString("key25")); //$NON-NLS-1$ + } + } + + // Extended low level break information + public static void appendBreakInfo(DebugCLI cli, StringBuilder sb, boolean includeFault, int isolateId) throws NotConnectedException + { + Session session = cli.getSession(); + FileInfoCache fileInfo = cli.getFileCache(); + + int reason = session.suspendReason(); + int offset = ((PlayerSession)session).getSuspendOffset(); + int index = ((PlayerSession)session).getSuspendActionIndex(); + + SwfInfo info = null; + try { info = fileInfo.getSwfs(isolateId)[index]; } catch(ArrayIndexOutOfBoundsException oobe) {} + if (info != null) + { + Map<String, String> args = new HashMap<String, String>(); + args.put("swfName", FileInfoCache.nameOfSwf(info) ); //$NON-NLS-1$ + sb.append(getLocalizationManager().getLocalizedTextString("key35", args)); //$NON-NLS-1$ + sb.append(' '); + } + + Map<String, String> args = new HashMap<String, String>(); + args.put("address", "0x" + FieldFormat.formatLongToHex(new StringBuilder(), offset, 8) + " (" + offset + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + sb.append(getLocalizationManager().getLocalizedTextString("atAddress", args)); //$NON-NLS-1$ + + if (includeFault) + { + args = new HashMap<String, String>(); + StringBuilder reasonBuffer = new StringBuilder(); + cli.appendReason(reasonBuffer, reason); + args.put("fault", reasonBuffer.toString() ); //$NON-NLS-1$ + sb.append(' '); + sb.append(getLocalizationManager().getLocalizedTextString("haltedDueToFault", args)); //$NON-NLS-1$ + } + } + + // Raw direct call to Player + public static void doShowVariable(DebugCLI cli) throws PlayerDebugException + { + int isolateId = cli.getActiveIsolateId(); + cli.waitTilHalted(isolateId); + try + { + // an integer followed by a variable name + Session session = cli.getSession(); + long id = cli.nextLongToken(); + String name = (cli.hasMoreTokens()) ? cli.nextToken() : null; + + StringBuilder sb = new StringBuilder(); + sb.append(name); + sb.append(" = "); //$NON-NLS-1$ + Value v = ((PlayerSession)session).getValue(id, name, isolateId); + cli.m_exprCache.appendVariableValue(sb, v, isolateId); + cli.out( sb.toString() ); + } + catch(NullPointerException npe) + { + cli.err(getLocalizationManager().getLocalizedTextString("key26")); //$NON-NLS-1$ + } + } + + public static void doDisassemble(DebugCLI cli) throws PlayerDebugException + { + /* currentXXX may NOT be invalid! */ + int currentModule = cli.propertyGet(DebugCLI.LIST_MODULE); + int currentLine = cli.propertyGet(DebugCLI.LIST_LINE); + int currentIsolate = cli.propertyGet(DebugCLI.LIST_WORKER); + + String arg1 = null; + int module1 = currentModule; + int line1 = currentLine; + + String arg2 = null; + int line2 = currentLine; + + boolean functionNamed = false; + int numLines = 0; + try + { + FileInfoCache fileInfo = cli.getFileCache(); + Session session = cli.getSession(); + int isolateId = cli.getActiveIsolateId(); + if (cli.hasMoreTokens()) + { + arg1 = cli.nextToken(); + if (arg1.equals("-")) //$NON-NLS-1$ + { + // move back one line + line1 = line2 = line1 - 1; + } + else + { + int wasFunc = 0; + + FileLocation[] fileLocations = cli.parseLocationArg(currentModule, currentLine, arg1, false); + + if (fileLocations.length == 1) { + module1 = fileLocations[0].getModule(); + line2 = line1 = fileLocations[0].getLine(); + functionNamed = (fileLocations[0].getWasFunc() != 0); + } + + if (cli.hasMoreTokens()) { + arg2 = cli.nextToken(); + line2 = cli.parseLineArg(module1, arg2); + } + } + } + else + { + // since no parms test for valid location if none use players concept of where we stopped + if( fileInfo.getFile(currentModule, currentIsolate) == null) + { + //here we simply use the players concept of suspsend + DSuspendInfo info = ((PlayerSession)session).getSuspendInfoIsolate(isolateId); + int at = info.getOffset(); + int which = info.getActionIndex(); + int until = info.getNextOffset(); + if (info.getReason() == SuspendReason.Unknown) + throw new SuspendedException(); + + SwfInfo swf = fileInfo.getSwfs(isolateId)[which]; + outputAssembly(cli, (DSwfInfo)swf, at, until); + throw new AmbiguousException(getLocalizationManager().getLocalizedTextString("key27")); //$NON-NLS-1$ + } + } + + /** + * Check for a few error conditions, otherwise we'll write a listing! + */ + if (cli.hasMoreTokens()) + { + cli.err(getLocalizationManager().getLocalizedTextString("key28")); //$NON-NLS-1$ + } + else + { + SourceFile file = fileInfo.getFile(module1); + numLines = file.getLineCount(); + + // pressing return is ok, otherwise throw the exception + if (line1 > numLines && arg1 != null) + throw new IndexOutOfBoundsException(); + + /* if no arg2 then user list a single line */ + if (arg2 == null) + line2 = line1; + + /* adjust our range of lines to ensure we conform */ + if (line1 < 1) + { + /* shrink line 1, grow line2 */ + line2 += -(line1 - 1); + line1 = 1; + } + + if (line2 > numLines) + line2 = numLines; + + // System.out.println("1="+module1+":"+line1+",2="+module2+":"+line2+",num="+numLines+",half="+half); + + /* nothing to display */ + if (line1 > line2) + throw new IndexOutOfBoundsException(); + + /* now dump the mixed source / assembly */ + // now lets find which swf this in + DSwfInfo swf = (DSwfInfo)fileInfo.swfForFile(file, cli.getActiveIsolateId()); + ActionLocation lStart = null; + ActionLocation lEnd = null; + + if (swf == null) + { + Map<String, String> args = new HashMap<String, String>(); + args.put("arg3", file.getName()); //$NON-NLS-1$ + cli.err(getLocalizationManager().getLocalizedTextString("key29", args)); //$NON-NLS-1$ + } + else if (functionNamed) + { + // if we name a function just dump the whole thing without source. + int offset = file.getOffsetForLine(line1); + lStart = swf.locate(offset); + if (lStart.function == null) + cli.err(getLocalizationManager().getLocalizedTextString("key30")); //$NON-NLS-1$ + else + { + // create a psudeo action list from which to disasemble the function + ActionList al = new ActionList(true); + al.setActionOffset(0, lStart.function); + lStart.actions = al; + lStart.at = 0; + lEnd = new ActionLocation(); + lEnd.actions = al; + lEnd.at = 0; + outputAssembly(cli, swf, lStart, lEnd); + } + } + else + { + ActionLocation lastEnd = null; + for(int i=line1; i<=line2; i++) + { + int offset = file.getOffsetForLine(i); + + // locate the action list associated with this of the swf + if (offset != 0) + { + // get the starting point and try to locate a nice ending + lStart = swf.locate(offset); + lEnd = swf.locateSourceLineEnd(lStart); + + // now see if we skipped some assembly between source lines + if (lastEnd != null) + { + lastEnd.at++; // point our pseudo start to the next action + + // new actions list so attempt to find the end of source in the old actions list + if (lastEnd.actions != lStart.actions && lastEnd.actions.size() != lastEnd.at) + { + String atString = Integer.toHexString(lastEnd.actions.getOffset(lastEnd.at)); + Map<String, String> args = new HashMap<String, String>(); + args.put("arg4", atString); //$NON-NLS-1$ + cli.out(getLocalizationManager().getLocalizedTextString("key31", args)); //$NON-NLS-1$ + + // we are missing some of the dissassembly, so back up a bit and dump it out + ActionLocation gapEnd = swf.locateSourceLineEnd(lastEnd); + outputAssembly(cli, swf, lastEnd, gapEnd); + } + else if (lastEnd.at < lStart.at) + { + // same action list but we skipped some instructions + ActionLocation gapEnd = new ActionLocation(lStart); + gapEnd.at--; + outputAssembly(cli, swf, lastEnd, gapEnd); + } + } + lastEnd = lEnd; + } + + // dump source + cli.outputSource(module1, i, file.getLine(i)); + + // obtain the offset, locate it in the swf + if (offset != 0) + outputAssembly(cli, swf, lStart, lEnd); + } + + /* save away valid context */ + cli.propertyPut(DebugCLI.LIST_MODULE, module1); + cli.propertyPut(DebugCLI.LIST_LINE, line2 + 1); // add one + cli.m_repeatLine = "disassemble"; /* allow repeated listing by typing CR */ //$NON-NLS-1$ + } + } + } + catch(IndexOutOfBoundsException iob) + { + String name = "#"+module1; //$NON-NLS-1$ + Map<String, String> args = new HashMap<String, String>(); + args.put("arg5", Integer.toString(line1)); //$NON-NLS-1$ + args.put("arg6", name); //$NON-NLS-1$ + args.put("arg7", Integer.toString(numLines)); //$NON-NLS-1$ + cli.err(getLocalizationManager().getLocalizedTextString("key32", args)); //$NON-NLS-1$ + } + catch(AmbiguousException ae) + { + cli.err(ae.getMessage()); + } + catch(NullPointerException npe) + { + cli.err(getLocalizationManager().getLocalizedTextString("key33")); //$NON-NLS-1$ + } + catch(ParseException pe) + { + cli.err(pe.getMessage()); + } + catch(NoMatchException nme) + { + cli.err(nme.getMessage()); + } + catch(SuspendedException se) + { + cli.err(getLocalizationManager().getLocalizedTextString("key34")); //$NON-NLS-1$ + } + } + + private static LocalizationManager getLocalizationManager() + { + return DebugCLI.getLocalizationManager(); + } + + /** + * Disassemble part of the swf to the output + */ + public static ActionLocation outputAssembly(DebugCLI cli, DSwfInfo swf, int start, int end) + { + // first we need to locate the action list associated with this + // portion of the swf + ActionLocation lStart = swf.locate(start); + ActionLocation lEnd = (end > -1) ? swf.locate(end) : swf.locateSourceLineEnd(lStart); + + return outputAssembly(cli, swf, lStart, lEnd); + } + + public static ActionLocation outputAssembly(DebugCLI cli, SwfInfo info, ActionLocation lStart, ActionLocation lEnd) + { + // now make sure our actions lists are the same (i.e we haven't spanned past one tag) + if (lStart.actions != lEnd.actions) + lEnd.at = lStart.actions.size()-1; + + Disassembler.disassemble(lStart.actions, lStart.pool, lStart.at, lEnd.at, new PrintWriter(cli.getOut())); + return lEnd; + } +} http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/07f5a7de/debugger/src/main/java/flex/tools/debugger/cli/ExtensionsDisabled.java ---------------------------------------------------------------------- diff --git a/debugger/src/main/java/flex/tools/debugger/cli/ExtensionsDisabled.java b/debugger/src/main/java/flex/tools/debugger/cli/ExtensionsDisabled.java new file mode 100644 index 0000000..caf33af --- /dev/null +++ b/debugger/src/main/java/flex/tools/debugger/cli/ExtensionsDisabled.java @@ -0,0 +1,37 @@ +/* + * 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; + +/** + * ExtensionsDisabled class is a singleton that contains + * every cli method that does not conform to the + * API. There are two implementations of this singleton + * In Extensions the full code is provided in this class + * ExtensionsDisabled emtpy stubs are provided that allow + * for DebugCLI to be fully compliant with the API + */ +public class ExtensionsDisabled +{ + public static void doShowStats(DebugCLI cli) { cli.out("Command not supported."); } //$NON-NLS-1$ + public static void doShowFuncs(DebugCLI cli) { cli.out("Command not supported."); } //$NON-NLS-1$ + public static void doShowProperties(DebugCLI cli) { cli.out("Command not supported."); } //$NON-NLS-1$ + public static void doShowBreak(DebugCLI cli) { cli.out("Command not supported."); } //$NON-NLS-1$ + public static void appendBreakInfo(DebugCLI cli, StringBuilder sb, boolean includeFault) { cli.out("Command not supported."); } //$NON-NLS-1$ + public static void doShowVariable(DebugCLI cli) { cli.out("Command not supported."); } //$NON-NLS-1$ + public static void doDisassemble(DebugCLI cli) { cli.out("Command not supported."); } //$NON-NLS-1$ +} http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/07f5a7de/debugger/src/main/java/flex/tools/debugger/cli/FaultActions.java ---------------------------------------------------------------------- diff --git a/debugger/src/main/java/flex/tools/debugger/cli/FaultActions.java b/debugger/src/main/java/flex/tools/debugger/cli/FaultActions.java new file mode 100644 index 0000000..73bc074 --- /dev/null +++ b/debugger/src/main/java/flex/tools/debugger/cli/FaultActions.java @@ -0,0 +1,228 @@ +/* + * 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.HashMap; + +import flash.localization.LocalizationManager; +import flash.tools.debugger.events.DivideByZeroFault; +import flash.tools.debugger.events.ExceptionFault; +import flash.tools.debugger.events.InvalidTargetFault; +import flash.tools.debugger.events.InvalidURLFault; +import flash.tools.debugger.events.InvalidWithFault; +import flash.tools.debugger.events.ProtoLimitFault; +import flash.tools.debugger.events.RecursionLimitFault; +import flash.tools.debugger.events.ScriptTimeoutFault; +import flash.tools.debugger.events.StackUnderFlowFault; + +/** + * FaultActions proivdes a convenient wrapper for housing the user specified + * behaviour for a set of faults (aka text strings) + * + * The underlying data structure is a HashMap that maps strings (i.e. fault + * names) to Integers. The integers are used as bit fields for holding + * the state of setting per fault. + * + * Add new actions by calling addAction("name") + */ +public class FaultActions +{ + HashMap<String, Integer> m_faults = new HashMap<String, Integer>(); + HashMap<String, String> m_description = new HashMap<String, String>(); // @todo should really use an object within the faults map for this + HashMap<String, Integer> m_actions = new HashMap<String, Integer>(); + + int m_nextBitForAction = 0x1; // the next bit to use for the action + + private FaultActions() {} + + Integer get(String o) { return m_faults.get(o); } + Integer getAction(String o) { return m_actions.get(o); } + void put(String k, Integer v){ m_faults.put(k,v); } + + /* getters */ + public void clear() { m_faults.clear(); } + public int size() { return m_faults.size(); } + public Object[] names() { return m_faults.keySet().toArray(); } + public Object[] actions() { return m_actions.keySet().toArray(); } + public boolean exists(String k) { return (get(k) == null) ? false : true; } + + public void putDescription(String k, String v) { m_description.put(k,v); } + public String getDescription(String k) { return (m_description.get(k) == null) ? "" : m_description.get(k); } //$NON-NLS-1$ + + /** + * Add a new fault to the table, with all actions disabled + */ + public void add(String k) + { + put(k, new Integer(0)); + } + + /** + * Add a new action type to the table + */ + public void addAction(String k) + { + Integer v = new Integer(m_nextBitForAction++); + m_actions.put(k,v); + } + + /** + * Check if the given fault has the action set or not + */ + public boolean is(String fault, String action) + { + int mask = getAction(action).intValue(); + int bits = get(fault).intValue(); + + boolean set = ( (bits & mask) == mask ) ? true : false; + return set; + } + + /** + * Sets the action bits as appropriate for the given fault + * and action + */ + public int action(String fault, String action) + { + // first check if fault is legal + Integer current = get(fault); + if (current == null) + throw new IllegalArgumentException(fault); + + // check for no? + boolean no = action.startsWith("no"); //$NON-NLS-1$ + if (no) + action = action.substring(2); + + // do the search for action + Integer bit = getAction(action); + if (bit == null) + throw new IllegalArgumentException(action); + + // now do the math + int old = current.intValue(); + int mask = bit.intValue(); + + int n = (old & (~mask)); // turn it off + n = (no) ? n : (n | mask); // leave it off or turn it on + + put(fault, new Integer(n)); + + return n; + } + + public static class FaultActionsBuilder { + + private final LocalizationManager localizationManager; + + public FaultActionsBuilder(LocalizationManager localizationManager) { + super(); + this.localizationManager = localizationManager; + } + + public FaultActions build() { + FaultActions faultActions = new FaultActions(); + populateFaultTable(faultActions); + return faultActions; + } + + private void populateFaultTable(FaultActions faultActions) { + // possible actions for our fault table + faultActions.addAction("stop"); //$NON-NLS-1$ + faultActions.addAction("print"); //$NON-NLS-1$ + + // the faults we support + faultActions.add(InvalidTargetFault.name); + faultActions.add(RecursionLimitFault.name); + faultActions.add(InvalidWithFault.name); + faultActions.add(ProtoLimitFault.name); + faultActions.add(InvalidURLFault.name); + faultActions.add(ExceptionFault.name); + faultActions.add(StackUnderFlowFault.name); + faultActions.add(DivideByZeroFault.name); + faultActions.add(ScriptTimeoutFault.name); + // faultActions.add(ConsoleErrorFault.name); + + // nice description of the faults + faultActions.putDescription( + InvalidTargetFault.name, + getLocalizationManager().getLocalizedTextString( + "invalidTargetFault")); //$NON-NLS-1$ + faultActions.putDescription( + RecursionLimitFault.name, + getLocalizationManager().getLocalizedTextString( + "recursionLimitFault")); //$NON-NLS-1$ + faultActions.putDescription( + InvalidWithFault.name, + getLocalizationManager().getLocalizedTextString( + "invalidWithFault")); //$NON-NLS-1$ + faultActions.putDescription( + ProtoLimitFault.name, + getLocalizationManager().getLocalizedTextString( + "protoLimitFault")); //$NON-NLS-1$ + faultActions.putDescription( + InvalidURLFault.name, + getLocalizationManager().getLocalizedTextString( + "invalidUrlFault")); //$NON-NLS-1$ + faultActions.putDescription( + ExceptionFault.name, + getLocalizationManager().getLocalizedTextString( + "exceptionFault")); //$NON-NLS-1$ + faultActions.putDescription( + StackUnderFlowFault.name, + getLocalizationManager().getLocalizedTextString( + "stackUnderflowFault")); //$NON-NLS-1$ + faultActions.putDescription( + DivideByZeroFault.name, + getLocalizationManager().getLocalizedTextString( + "divideByZeroFault")); //$NON-NLS-1$ + faultActions.putDescription( + ScriptTimeoutFault.name, + getLocalizationManager().getLocalizedTextString( + "scriptTimeoutFault")); //$NON-NLS-1$ + // faultActions.putDescription(ConsoleErrorFault.name, + // "ActionScript recoverable error"); + + // default values for the faults + faultActions.action(InvalidTargetFault.name, "stop"); //$NON-NLS-1$ + faultActions.action(InvalidTargetFault.name, "print"); //$NON-NLS-1$ + faultActions.action(RecursionLimitFault.name, "stop"); //$NON-NLS-1$ + faultActions.action(RecursionLimitFault.name, "print"); //$NON-NLS-1$ + faultActions.action(InvalidWithFault.name, "stop"); //$NON-NLS-1$ + faultActions.action(InvalidWithFault.name, "print"); //$NON-NLS-1$ + faultActions.action(ProtoLimitFault.name, "stop"); //$NON-NLS-1$ + faultActions.action(ProtoLimitFault.name, "print"); //$NON-NLS-1$ + faultActions.action(InvalidURLFault.name, "stop"); //$NON-NLS-1$ + faultActions.action(InvalidURLFault.name, "print"); //$NON-NLS-1$ + faultActions.action(ExceptionFault.name, "stop"); //$NON-NLS-1$ + faultActions.action(ExceptionFault.name, "print"); //$NON-NLS-1$ + faultActions.action(StackUnderFlowFault.name, "stop"); //$NON-NLS-1$ + faultActions.action(StackUnderFlowFault.name, "print"); //$NON-NLS-1$ + faultActions.action(DivideByZeroFault.name, "stop"); //$NON-NLS-1$ + faultActions.action(DivideByZeroFault.name, "print"); //$NON-NLS-1$ + faultActions.action(ScriptTimeoutFault.name, "stop"); //$NON-NLS-1$ + faultActions.action(ScriptTimeoutFault.name, "print"); //$NON-NLS-1$ + // faultActions.action(ConsoleErrorFault.name, "print"); //$NON-NLS-1$ + // faultActions.action(ConsoleErrorFault.name, "stop"); //$NON-NLS-1$ + } + + private LocalizationManager getLocalizationManager() { + return localizationManager; + } + } +} http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/07f5a7de/debugger/src/main/java/flex/tools/debugger/cli/FileInfoCache.java ---------------------------------------------------------------------- diff --git a/debugger/src/main/java/flex/tools/debugger/cli/FileInfoCache.java b/debugger/src/main/java/flex/tools/debugger/cli/FileInfoCache.java new file mode 100644 index 0000000..e66b89a --- /dev/null +++ b/debugger/src/main/java/flex/tools/debugger/cli/FileInfoCache.java @@ -0,0 +1,569 @@ +/* + * 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.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; + +import flash.tools.debugger.InProgressException; +import flash.tools.debugger.Isolate; +import flash.tools.debugger.NoResponseException; +import flash.tools.debugger.Session; +import flash.tools.debugger.SourceFile; +import flash.tools.debugger.SwfInfo; +import flash.util.IntMap; + +/** + * FileInfoCache manages a list of files that are unique + * across multiple swfs. + */ +public class FileInfoCache implements Comparator<SourceFile> +{ + Session m_session; + + /** + * We can get at files by module id or path + */ + IntMap m_byInt = new IntMap(); + HashMap<Integer, IntMap> m_isolateState = new HashMap<Integer, IntMap> (); + + private IntMap getIsolateState(int isolateId) { + IntMap isolateState = null; + if (!m_isolateState.containsKey(isolateId)) { + isolateState = new IntMap(); + m_isolateState.put(isolateId, isolateState); + } + else + isolateState = m_isolateState.get(isolateId); + return isolateState; + } + + SourceFile[] m_files = null; + SourceFile[] m_isolateFiles = null; + SwfInfo m_swfFilter = null; + int m_swfsLoaded = 0; + boolean m_dirty = false; + int lastActiveIsolate = Isolate.DEFAULT_ID; + + public FileInfoCache() { + + } + + public void bind(Session s) { setSession(s); } + public void unbind() { m_session = null; } + + public SourceFile getFile(int i) { + return getFile(i, Isolate.DEFAULT_ID); + } + + public SourceFile getFile(int i, int isolateId) { + populate(); + if (isolateId == Isolate.DEFAULT_ID) + return (SourceFile) m_byInt.get(i); + else + return (SourceFile)getIsolateState(isolateId).get(i); + } + + public SourceFile[] getFileList() { + populate(); + return m_files; + } + + public SourceFile[] getFileList(int isolateId) { + populate(); + if (isolateId == Isolate.DEFAULT_ID) { + final Object[] valuesToArray = m_byInt.valuesToArray(new Object[m_byInt.size()]); + return Arrays.copyOf(valuesToArray, valuesToArray.length, SourceFile[].class); + } + else if (isolateId != lastActiveIsolate) { + buildIsolateFiles(isolateId); + } + return m_isolateFiles; + } + + private void buildIsolateFiles(int isolateId) { + SwfInfo[] swfs = getSwfs(isolateId); + boolean worked = true; // check that all worked correctly + ArrayList<SourceFile> files = new ArrayList<SourceFile>(); + + for(int i=0; i<swfs.length; i++) + { + if (swfs[i] != null) + worked = loadSwfFiles(files, swfs[i]) ? worked : false; + } + + // trim the file list + ArrayList<SourceFile> fa = trimFileList(files); + m_isolateFiles = fa.toArray( new SourceFile[fa.size()] ); + + // sort this array in place so calls to getFileList will be ordered + Arrays.sort(m_isolateFiles, this); + } + + public Iterator getAllFiles(int isolateId) { + populate(); + if (isolateId == Isolate.DEFAULT_ID) + return m_byInt.iterator(); + else + return getIsolateState(isolateId).iterator(); + } + + public SwfInfo getSwfFilter() { return m_swfFilter; } + public boolean isSwfFilterOn() { return (m_swfFilter != null); } + public void setDirty() { m_dirty = true; } + + void setSession(Session s) + { + m_session = s; + m_swfFilter = null; + clear(); + } + + SwfInfo[] getAllSwfs() { + ArrayList<SwfInfo> result = new ArrayList<SwfInfo>(); + + for ( Isolate isolate : m_session.getWorkers()) { + SwfInfo[] swfs = new SwfInfo[0]; + try { + swfs = m_session.getWorkerSession(isolate.getId()).getSwfs(); + } catch (NoResponseException e) { + swfs = new SwfInfo[0]; + } + + for (SwfInfo swf : swfs) + result.add(swf); + } + + return result.toArray(new SwfInfo[0]); + } + + void populate() + { + // do we have a new swf to load? + if (m_session != null && (m_dirty || getAllSwfs().length > m_swfsLoaded)) + reloadCache(); + } + + void reloadCache() + { + clear(); + loadCache(); + m_dirty = false; + } + + void clear() + { + m_byInt.clear(); + m_isolateState.clear(); + m_files = null; + } + + /** + * Determine if the given SourceFile is in the current fileList + */ + public boolean inFileList(SourceFile f) + { + boolean isIt = false; + + SourceFile[] files = getFileList(); + for(int i=0; i<files.length && !isIt; i++) + { + if (files[i] == f) + isIt = true; + } + return isIt; + } + + /** + * Go out to the session and request a list of files + * But we dump ones that have a name collision. + * Also if selectedSwf is set then we only add files + * that are contained within the given swf. + */ + void loadCache() + { + boolean worked = true; // check that all worked correctly + ArrayList<SourceFile> files = new ArrayList<SourceFile>(); + SwfInfo[] swfs = getAllSwfs(); + for(int i=0; i<swfs.length; i++) + { + if (swfs[i] != null) + worked = loadSwfFiles(files, swfs[i]) ? worked : false; + } + + // trim the file list + ArrayList<SourceFile> fa = trimFileList(files); + m_files = fa.toArray( new SourceFile[fa.size()] ); + + // sort this array in place so calls to getFileList will be ordered + Arrays.sort(m_files, this); + + // mark our cache complete if all was good. + if (worked) + m_swfsLoaded = swfs.length; + } + + boolean loadSwfFiles(ArrayList<SourceFile> ar, SwfInfo swf) + { + boolean worked = true; + try + { + // @todo should we include unloaded swfs? + SourceFile[] files = swf.getSourceList(m_session); + ar.ensureCapacity(ar.size()+files.length); + + // add each file to our global source file IntMap and our list + for(int i=0; i<files.length; i++) + { + putFile(files[i], swf.getIsolateId()); + ar.add(files[i]); + } + } + catch(InProgressException ipe) + { + // can't load this one, its not ready yet + worked = false; + } + return worked; + } + + /** + * Walk the file list looking for name collisions. + * If we find one, then we remove it + */ + ArrayList<SourceFile> trimFileList(ArrayList<SourceFile> files) + { + HashMap<String, String> names = new HashMap<String, String>(); + ArrayList<SourceFile> list = new ArrayList<SourceFile>(); + + int size = files.size(); + for(int i=0; i<size; i++) + { + boolean addIt = false; + + SourceFile fi = files.get(i); + // no filter currently in place so we add the file as long + // as no duplicates exist. We use the original Swd full + // name for matching. + String fName = fi.getRawName(); + if (m_swfFilter == null) + { + // If it exists, then we don't add it! + if (names.get(fName) == null) + addIt = true; + } + else + { + // we have a filter in place so, see + // if the source file is in our currently + // selected swf. + addIt = m_swfFilter.containsSource(fi); + } + + // did we mark this one to add? + if (addIt) + { + names.put(fName, fName); + list.add(fi); + } + } + return list; + } + + /** + * All files from all swfs are placed into our byInt map + * since we know that ids are unique across the entire + * Player session. + * + * This is also important in the case that the player + * halts in one of these files, that the debugger + * be able to locate the SourceFile so that we can + * display the correct context for the user. + */ + void putFile(SourceFile s, int isolateId) + { + int i = s.getId(); + if (isolateId == Isolate.DEFAULT_ID) + m_byInt.put(i, s); + else + getIsolateState(isolateId).put(i, s); + } + + /** + * Attempt to set a swf as a filter + * for the file list that we create + */ + public boolean setSwfFilter(String swfName) + { + // look for a match in our list + boolean worked = false; + if (swfName == null) + { + m_swfFilter = null; + worked = true; + } + else + { + SwfInfo[] swfs = getAllSwfs(); + for(int i=0; i<swfs.length; i++) + { + SwfInfo e = swfs[i]; + if (e != null && nameOfSwf(e).equalsIgnoreCase(swfName)) + { + worked = true; + m_swfFilter = e; + break; + } + } + } + + // reload if it worked + if (worked) + reloadCache(); + + return worked; + } + + // list all swfs we know about + public SwfInfo[] getSwfs(int isolateId) + { + return getSwfsIsolate(isolateId); + } + + public SwfInfo[] getSwfsIsolate(int isolateId) + { + SwfInfo[] swfs = null; + try + { + swfs = m_session.getWorkerSession(isolateId).getSwfs(); + } + catch(NoResponseException nre) + { + swfs = new SwfInfo[] {}; // oh bery bad + } + return swfs; + } + + /** + * Given a SourceFile locate the swf which it came from + */ + public SwfInfo swfForFile(SourceFile f, int isolateId) + { + // We use the id to determine which swf this source files resides in + int id = f.getId(); + SwfInfo info = null; + SwfInfo[] swfs = getSwfs(isolateId);//getAllSwfs(); + for(int i=0; ( i<swfs.length && (info == null) ); i++) + { + if (swfs[i] != null && swfs[i].containsSource(f)) + info = swfs[i]; + } + return info; + } + + // locate the name of the swf + public static String nameOfSwf(SwfInfo e) + { + int at = -1; + String name = e.getUrl(); + if ( (at = e.getUrl().lastIndexOf('/')) > -1) + name = e.getUrl().substring(at+1); + if ( (at = e.getUrl().lastIndexOf('\\')) > -1) + name = e.getUrl().substring(at+1); + else if ( (at = e.getPath().lastIndexOf('\\')) > -1) + name = e.getPath().substring(at+1); + else if ( (at = e.getPath().lastIndexOf('/')) > -1) + name = e.getPath().substring(at+1); + + // now rip off any trailing ? options + at = name.lastIndexOf('?'); + name = (at > -1) ? name.substring(0, at) : name; + + return name; + } + + // locate the name of the swf + public static String shortNameOfSwf(SwfInfo e) + { + String name = nameOfSwf(e); + + // now strip off any leading path + int at = -1; + if ( (at = name.lastIndexOf('/')) > -1) + name = name.substring(at+1); + else if ( (at = name.lastIndexOf('\\')) > -1) + name = name.substring(at+1); + return name; + } + + /** + * Given the URL of a specfic swf determine + * if there is a file within it that appears + * to be the same as the given source file + * @param f + * @return + */ + public SourceFile similarFileInSwf(SwfInfo info, SourceFile f) throws InProgressException + { + SourceFile hit = null; + SourceFile[] files = info.getSourceList(m_session); + if (!info.isProcessingComplete()) + throw new InProgressException(); + + for(int i=0; i<files.length; i++) + { + if (filesMatch(f, files[i])) + hit = files[i]; + } + return hit; + } + + /** + * Comparator interface for sorting SourceFiles + */ + public int compare(SourceFile o1, SourceFile o2) + { + String n1 = o1.getName(); + String n2 = o2.getName(); + + return n1.compareTo(n2); + } + + /** + * Compare two files and determine if they are the same. + * Our criteria included only line count package names + * and the name of the class itself. If there are + * any other differences then we won't be able to detect + * them. We should probably do something like an MD5 + * computation on the characters in ScriptText. Then + * we'd really be sure of a match. + * @param a first file to compare + * @param b second file to compare + * @return true if files appear to be the same + */ + public boolean filesMatch(SourceFile a, SourceFile b) + { + boolean yes = true; + + if (a == null || b == null) + yes = false; + else if (a.getPackageName().compareTo(b.getPackageName()) != 0) + yes = false; + else if (a.getName().compareTo(b.getName()) != 0) + yes = false; + else if (a.getLineCount() != b.getLineCount()) // warning, this is sometimes expensive, so do it last + yes = false; + + return yes; + } + /** + * Return a array of SourceFiles whose names match + * the specified string. The array is sorted by name. + * The input can be mx.controls.xxx which will + */ + public SourceFile[] getFiles(String matchString) + { + return getFiles(matchString, -1); + } + + public SourceFile[] getFiles(String matchString, int isolateId) + { + boolean doStartsWith = false; + boolean doIndexOf = false; + boolean doEndsWith = false; + + boolean leadingAsterisk = matchString.startsWith("*") && matchString.length() > 1; //$NON-NLS-1$ + boolean trailingAsterisk = matchString.endsWith("*"); //$NON-NLS-1$ + boolean usePath = matchString.indexOf('.') > -1; + + if (leadingAsterisk && trailingAsterisk) + { + matchString = matchString.substring(1, matchString.length() - 1); + doIndexOf = true; + } + else if (leadingAsterisk) + { + matchString = matchString.substring(1); + doEndsWith = true; + } + else if (trailingAsterisk) + { + matchString = matchString.substring(0, matchString.length() - 1); + doStartsWith = true; + } + else if (usePath) + { + doIndexOf = true; + } + else + { + doStartsWith = true; + } + + SourceFile[] files = isolateId > -1 ? getFileList(isolateId) : getFileList(); + ArrayList<SourceFile> fileList = new ArrayList<SourceFile>(); + int n = files.length; + int exactHitAt = -1; + // If the matchString already starts with "." (e.g. ".as" or ".mxml"), then dotMatchString + // will be equal to matchString; otherwise, dotMatchString will be "." + matchString + String dotMatchString = (matchString.startsWith(".")) ? matchString : ("." + matchString); //$NON-NLS-1$ //$NON-NLS-2$ + for (int i = 0; i < n; i++) + { + SourceFile sourceFile = files[i]; + boolean pathExists = (usePath && sourceFile.getFullPath().matches(".*[/\\\\].*")); //$NON-NLS-1$ + String name = pathExists ? sourceFile.getFullPath() : sourceFile.getName(); + + // if we are using the full path string, then prefix a '.' to our matching string so that abc.as and Gabc.as don't both hit + String match = (usePath && pathExists) ? dotMatchString : matchString; + + match = match.replace('/', '.'); // get rid of path identifiers and use dots + match = match.replace('\\', '.'); + + name = name.replace('/', '.'); // get rid of path identifiers and use dots + name = name.replace('\\', '.'); // would be better to modify the input string, but we don't know which path char will be used. + + // exact match? We are done + if (name.equals(match)) + { + exactHitAt = i; + break; + } + else if (doStartsWith && name.startsWith(match)) + fileList.add(sourceFile); + else if (doEndsWith && name.endsWith(match)) + fileList.add(sourceFile); + else if (doIndexOf && name.contains(match)) + fileList.add(sourceFile); + } + + // trim all others if we have an exact file match + if (exactHitAt > -1) + { + fileList.clear(); + fileList.add(files[exactHitAt]); + } + + SourceFile[] fileArray = fileList.toArray( new SourceFile[fileList.size()] ); + Arrays.sort(fileArray, this); + return fileArray; + } + +}
