| /* |
| * 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 flash.tools.debugger.concrete; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import flash.tools.debugger.Isolate; |
| import flash.tools.debugger.NoResponseException; |
| import flash.tools.debugger.NotConnectedException; |
| import flash.tools.debugger.NotSuspendedException; |
| import flash.tools.debugger.Session; |
| import flash.tools.debugger.Value; |
| import flash.tools.debugger.ValueAttribute; |
| import flash.tools.debugger.Variable; |
| import flash.tools.debugger.VariableType; |
| import flash.tools.debugger.expression.Context; |
| |
| /** |
| * Implementation of an ActionScript value. |
| */ |
| public class DValue implements Value |
| { |
| /** @see VariableType */ |
| private int m_type; |
| |
| /** @see Variable#getTypeName() */ |
| private String m_typeName; |
| |
| /** @see Variable#getClassName() */ |
| private String m_className; |
| |
| /** @see ValueAttribute */ |
| private int m_attribs; |
| |
| /** Maps "varname" (without its namespace) to a Variable */ |
| private Map<String, DVariable> m_members; |
| |
| /** |
| * Either my own ID, or else my parent's ID if I am <code>__proto__</code>. |
| */ |
| long m_nonProtoId; |
| |
| /** |
| * <code>m_value</code> can have one of several possible meanings: |
| * |
| * <ul> |
| * <li> If this variable's value is an <code>Object</code> or a <code>MovieClip</code>, |
| * then <code>m_value</code> contains the ID of the <code>Object</code> or |
| * <code>MovieClip</code>, stored as a <code>Long</code>. </li> |
| * <li> If this variable refers to a Getter which has not yet been invoked, then |
| * <code>m_value</code> contains the ID of the Getter, stored as a |
| * <code>Long</code>. </li> |
| * <li> If this variable's value is <code>undefined</code>, then <code>m_value</code> |
| * will be equal to <code>Value.UNDEFINED</code>. |
| * <li> Otherwise, this variable's value is a simple type such as <code>int</code> or |
| * <code>String</code>, in which case <code>m_value</code> holds the actual value. |
| * </ul> |
| */ |
| private Object m_value; |
| |
| /** |
| * The list of classes that contributed members to this object, from |
| * the class itself all the way down to Object. |
| */ |
| private String[] m_classHierarchy; |
| |
| /** |
| * How many members of <code>m_classHierarchy</code> actually contributed |
| * members to this object. |
| */ |
| private int m_levelsWithMembers; |
| |
| private Session m_session; |
| |
| /** Maps duplicate private "varname" to a list of Variable objects */ |
| private Map<String, List<DVariable>> m_inheritedPrivates; |
| |
| private int m_isolateId; |
| |
| |
| /** |
| * Create a top-level variable which has no parent. This may be used for |
| * _global, _root, stack frames, etc. |
| * |
| * @param id the ID of the variable |
| */ |
| public DValue(long id, int isolateId) |
| { |
| init(VariableType.UNKNOWN, null, null, 0, new Long(id)); |
| setIsolateId(isolateId); |
| } |
| |
| /** |
| * Create a value. |
| * |
| * @param type see <code>VariableType</code> |
| * @param typeName |
| * @param className |
| * @param attribs |
| * the attributes of this value; see <code>ValueAttribute</code> |
| * @param value |
| * for an Object or MovieClip, this should be a Long which contains the |
| * ID of this variable. For a variable of any other type, such as integer |
| * or string, this should be the value of the variable. |
| * @param isolateId |
| * the worker to which this value belongs |
| */ |
| public DValue(int type, String typeName, String className, int attribs, Object value, int isolateId) |
| { |
| init(type, typeName, className, attribs, value); |
| setIsolateId(isolateId); |
| } |
| |
| /** |
| * Constructs a DValue for a primitive value (null, undefined, Boolean, Number, String). |
| * |
| * There is nothing special about these objects -- it would be just as legitimate for |
| * anyone who wants a Value for a primitive to make their own subclass of Value. |
| */ |
| public static DValue forPrimitive(Object primitiveValue, int isolateId) |
| { |
| if (primitiveValue == null) |
| return new DValue(VariableType.NULL, "null", "", 0, primitiveValue, isolateId); //$NON-NLS-1$ //$NON-NLS-2$ |
| else if (primitiveValue == Value.UNDEFINED) |
| return new DValue(VariableType.UNDEFINED, "undefined", "", 0, primitiveValue, isolateId); //$NON-NLS-1$ //$NON-NLS-2$ |
| else if (primitiveValue instanceof Boolean) |
| return new DValue(VariableType.BOOLEAN, "Boolean", "", 0, primitiveValue, isolateId); //$NON-NLS-1$ //$NON-NLS-2$ |
| else if (primitiveValue instanceof Double) |
| return new DValue(VariableType.NUMBER, "Number", "", 0, primitiveValue, isolateId); //$NON-NLS-1$ //$NON-NLS-2$ |
| else if (primitiveValue instanceof String) |
| return new DValue(VariableType.STRING, "String", "", 0, primitiveValue, isolateId); //$NON-NLS-1$ //$NON-NLS-2$ |
| assert false; |
| return null; |
| } |
| |
| /** |
| * Initialize a variable. |
| * |
| * For the meanings of the arguments, see the DVariable constructor. |
| */ |
| private void init(int type, String typeName, String className, int attribs, Object value) |
| { |
| if (value == null && type == VariableType.UNDEFINED) |
| value = Value.UNDEFINED; |
| |
| m_type = type; |
| m_typeName = typeName; |
| m_className = className; |
| m_attribs = attribs; |
| m_value = value; |
| m_members = null; |
| m_inheritedPrivates = null; |
| m_nonProtoId = getId(); |
| m_isolateId = Isolate.DEFAULT_ID; |
| } |
| |
| public int getIsolateId() { |
| return m_isolateId; |
| } |
| |
| public void setIsolateId(int isolateid) { |
| m_isolateId = isolateid; |
| } |
| |
| /* |
| * @see flash.tools.debugger.Value#getAttributes() |
| */ |
| public int getAttributes() |
| { |
| return m_attribs; |
| } |
| |
| /* |
| * @see flash.tools.debugger.Value#getClassName() |
| */ |
| public String getClassName() |
| { |
| return m_className; |
| } |
| |
| /* |
| * @see flash.tools.debugger.Value#getId() |
| */ |
| public long getId() |
| { |
| // see if we support an id concept |
| if (m_value instanceof Long) |
| return ((Long)m_value).longValue(); |
| else |
| return Value.UNKNOWN_ID; |
| } |
| |
| /* |
| * @see flash.tools.debugger.Value#getMemberCount(flash.tools.debugger.Session) |
| */ |
| public int getMemberCount(Session s) throws NotSuspendedException, |
| NoResponseException, NotConnectedException |
| { |
| obtainMembers(s); |
| return (m_members == null) ? 0 : m_members.size(); |
| } |
| |
| /* |
| * @see flash.tools.debugger.Value#getMemberNamed(flash.tools.debugger.Session, java.lang.String) |
| */ |
| public Variable getMemberNamed(Session s, String name) |
| throws NotSuspendedException, NoResponseException, |
| NotConnectedException |
| { |
| obtainMembers(s); |
| return findMember(name); |
| } |
| |
| /* |
| * @see flash.tools.debugger.Value#getClassHierarchy(boolean) |
| */ |
| public String[] getClassHierarchy(boolean allLevels) { |
| if (allLevels) { |
| return m_classHierarchy; |
| } else { |
| String[] partialClassHierarchy; |
| |
| if (m_classHierarchy != null) |
| { |
| partialClassHierarchy = new String[m_levelsWithMembers]; |
| System.arraycopy(m_classHierarchy, 0, partialClassHierarchy, 0, m_levelsWithMembers); |
| } |
| else |
| { |
| partialClassHierarchy = new String[0]; |
| } |
| return partialClassHierarchy; |
| } |
| } |
| |
| /* TODO should this really be public? */ |
| public DVariable findMember(String named) |
| { |
| if (m_members == null) |
| return null; |
| else |
| return m_members.get(named); |
| } |
| |
| /* |
| * @see flash.tools.debugger.Value#getMembers(flash.tools.debugger.Session) |
| */ |
| public Variable[] getMembers(Session s) throws NotSuspendedException, |
| NoResponseException, NotConnectedException |
| { |
| obtainMembers(s); |
| |
| /* find out the size of the array */ |
| int count = getMemberCount(s); |
| DVariable[] ar = new DVariable[count]; |
| |
| if (count > 0) |
| { |
| count = 0; |
| Iterator<DVariable> itr = m_members.values().iterator(); |
| while(itr.hasNext()) |
| { |
| DVariable sf = itr.next(); |
| ar[count++] = sf; |
| } |
| |
| // sort the member list by name |
| Arrays.sort(ar); |
| } |
| |
| return ar; |
| } |
| |
| /** |
| * WARNING: this call will initiate a call to the session to obtain the members |
| * the first time around. |
| * @throws NotConnectedException |
| * @throws NoResponseException |
| * @throws NotSuspendedException |
| */ |
| private void obtainMembers(Session s) throws NotSuspendedException, NoResponseException, NotConnectedException |
| { |
| if (s == null) |
| s = m_session; |
| else |
| m_session = s; |
| |
| if (m_members == null && s != null) |
| { |
| // performing a get on this variable obtains all its members |
| long id = getId(); |
| if (id != Value.UNKNOWN_ID) |
| { |
| if (((PlayerSession)s).getRawValue(id, m_isolateId) == this) |
| ((PlayerSession)s).obtainMembers(id, m_isolateId); |
| if (m_members != null) |
| { |
| Iterator<DVariable> iter = m_members.values().iterator(); |
| while (iter.hasNext()) |
| { |
| Object next = iter.next(); |
| if (next instanceof DVariable) |
| { |
| ((DVariable)next).setSession(s); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| public boolean membersObtained() |
| { |
| return (getId() == UNKNOWN_ID || m_members != null); |
| } |
| |
| public void setMembersObtained(boolean obtained) |
| { |
| if (obtained) |
| { |
| if (m_members == null) |
| m_members = Collections.emptyMap(); |
| if (m_inheritedPrivates == null) |
| m_inheritedPrivates = Collections.emptyMap(); |
| } |
| else |
| { |
| m_members = null; |
| m_inheritedPrivates = null; |
| } |
| } |
| |
| public void addMember(DVariable v) |
| { |
| if (m_members == null) |
| m_members = new HashMap<String, DVariable>(); |
| |
| // if we are a proto member house away our original parent id |
| String name = v.getName(); |
| DValue val = (DValue) v.getValue(); |
| val.m_nonProtoId = (name != null && name.equals("__proto__")) ? m_nonProtoId : val.getId(); //$NON-NLS-1$ // TODO is this right? |
| v.m_nonProtoParentId = m_nonProtoId; |
| |
| m_members.put(name, v); |
| } |
| |
| public void addInheritedPrivateMember(DVariable v) |
| { |
| if (m_inheritedPrivates == null) |
| m_inheritedPrivates = new HashMap<String, List<DVariable>>(); |
| |
| // if we are a proto member house away our original parent id |
| String name = v.getName(); |
| DValue val = (DValue) v.getValue(); |
| val.m_nonProtoId = (name != null && name.equals("__proto__")) ? m_nonProtoId : val.getId(); //$NON-NLS-1$ // TODO is this right? |
| v.m_nonProtoParentId = m_nonProtoId; |
| List<DVariable> resultList = m_inheritedPrivates.get(name); |
| if (resultList == null) { |
| resultList = new ArrayList<DVariable>(); |
| resultList.add(v); |
| m_inheritedPrivates.put(name, resultList); |
| } |
| else |
| resultList.add(v); |
| //m_inheritedPrivates.put(name, v); |
| } |
| |
| public void removeAllMembers() |
| { |
| m_members = null; |
| m_inheritedPrivates = null; |
| } |
| |
| /* |
| * @see flash.tools.debugger.Value#getType() |
| */ |
| public int getType() |
| { |
| return m_type; |
| } |
| |
| /* |
| * @see flash.tools.debugger.Value#getTypeName() |
| */ |
| public String getTypeName() |
| { |
| return m_typeName; |
| } |
| |
| /* |
| * @see flash.tools.debugger.Value#getValueAsObject() |
| */ |
| public Object getValueAsObject() |
| { |
| return m_value; |
| } |
| |
| /* |
| * @see flash.tools.debugger.Value#getValueAsString() |
| */ |
| public String getValueAsString() |
| { |
| return getValueAsString(m_value); |
| } |
| |
| /** |
| * @param value an object which might be one of these types: |
| * Boolean, Integer, Long, Double, String, Value.UNDEFINED (representing |
| * the value 'undefined'); or null. |
| */ |
| public static String getValueAsString(Object value) |
| { |
| if (value == null) |
| return "null"; //$NON-NLS-1$ |
| |
| if (value instanceof Double) |
| { |
| // Java often formats whole numbers in ugly ways. For example, |
| // the number 3 might be formatted as "3.0" and, even worse, |
| // the number 12345678 might be formatted as "1.2345678E7" ! |
| // So, if the number has no fractional part, then we override |
| // the default display behavior. |
| double doubleValue = ((Double)value).doubleValue(); |
| long longValue = (long) doubleValue; |
| if (doubleValue == longValue) |
| return Long.toString(longValue); |
| } |
| |
| return value.toString(); |
| } |
| |
| /* |
| * @see flash.tools.debugger.Value#isAttributeSet(int) |
| */ |
| public boolean isAttributeSet(int variableAttribute) |
| { |
| return (m_attribs & variableAttribute) != 0; |
| } |
| |
| public void setTypeName(String s) { m_typeName = s; } |
| public void setClassName(String s) { m_className = s; } |
| public void setType(int t) { m_type = t; } |
| public void setValue(Object o) { m_value = o; } |
| public void setAttributes(int f) { m_attribs = f; } |
| |
| public void setClassHierarchy(String[] classHierarchy, int levelsWithMembers) |
| { |
| m_classHierarchy = classHierarchy; |
| m_levelsWithMembers = levelsWithMembers; |
| } |
| |
| public String membersToString() |
| { |
| StringBuilder sb = new StringBuilder(); |
| |
| /* find out the size of the array */ |
| if (m_members == null) |
| sb.append(PlayerSessionManager.getLocalizationManager().getLocalizedTextString("empty")); //$NON-NLS-1$ |
| else |
| { |
| Iterator<DVariable> itr = m_members.values().iterator(); |
| while(itr.hasNext()) |
| { |
| DVariable sf = itr.next(); |
| sb.append(sf); |
| sb.append(",\n"); //$NON-NLS-1$ |
| } |
| } |
| return sb.toString(); |
| } |
| |
| public void setSession(Session s) |
| { |
| m_session = s; |
| } |
| |
| /** |
| * Necessary for expression evaluation. |
| * @see Context#lookup(Object) |
| */ |
| @Override |
| public String toString() { return getValueAsString(); } |
| |
| public Variable[] getPrivateInheritedMembers() { |
| if (m_inheritedPrivates == null) |
| return new DVariable[0]; |
| |
| ArrayList<DVariable> finalList = new ArrayList<DVariable>(); |
| |
| Iterator<List<DVariable>> itr = m_inheritedPrivates.values().iterator(); |
| while(itr.hasNext()) |
| { |
| List<DVariable> varList = itr.next(); |
| finalList.addAll(varList); |
| } |
| |
| DVariable[] ar = finalList.toArray(new DVariable[0]); |
| // sort the member list by name |
| Arrays.sort(ar); |
| |
| return ar; |
| } |
| |
| public Variable[] getPrivateInheritedMemberNamed(String name) { |
| if (m_inheritedPrivates == null) |
| return new DVariable[0]; |
| List<DVariable> list = m_inheritedPrivates.get(name); |
| if (list != null) { |
| return list.toArray(new Variable[0]); |
| } |
| return new DVariable[0]; |
| } |
| } |