blob: 2652ba1a17f6f34889e0d461dd6a4ebd4bbdf89d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package 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];
}
}