package com.intellij.lang.javascript.flex.debug;
import com.intellij.javascript.flex.mxml.MxmlJSClass;
import com.intellij.javascript.flex.resolve.ActionScriptClassResolver;
import com.intellij.lang.javascript.index.JavaScriptIndex;
import com.intellij.lang.javascript.psi.*;
import com.intellij.lang.javascript.psi.ecmal4.JSClass;
import com.intellij.lang.javascript.psi.resolve.JSInheritanceUtil;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.ProjectScope;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.ArrayUtil;
import com.intellij.util.PlatformIcons;
import com.intellij.xdebugger.XDebuggerUtil;
import com.intellij.xdebugger.XSourcePosition;
import com.intellij.xdebugger.evaluation.XDebuggerEvaluator;
import com.intellij.xdebugger.frame.*;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.debugger.DebuggerSupportUtils;
import javax.swing.*;
import java.util.*;
class FlexValue extends XValue {
private FlexStackFrame myFlexStackFrame;
private final FlexDebugProcess myDebugProcess;
private final @Nullable XSourcePosition mySourcePosition;
private final String myName;
private final String myExpression;
private final String myResult;
private @Nullable final String myParentResult;
private final ValueType myValueType;
private Icon myPreferredIcon;
private static final String OBJECT_MARKER = "Object ";
private static final String XML_TYPE = "XML";
private static final String XMLLIST_TYPE = "XMLList";
static final String TEXT_MARKER = " text ";
static final String ELEMENT_MARKER = " element ";
private static final String ESCAPE_START = "IDEA-ESCAPE-START";
private static final String ESCAPE_END = "IDEA-ESCAPE-END";
private static final String VECTOR_PREFIX = "__AS3__.vec::";
private static final String VECTOR = "Vector";
private static final String GENERIC_VECTOR_PREFIX = "Vector.<";
private static final String[] COLLECTIONS_WITH_DIRECT_CONTENT = {
"Array",
VECTOR,
"mx.collections.ListCollectionView",
"mx.collections.ArrayCollection",
"mx.collections.XMLListCollection",
};
private static final String[] COLLECTION_CLASSES = ArrayUtil.mergeArrays(
COLLECTIONS_WITH_DIRECT_CONTENT,
"mx.collections.ArrayList",
"mx.collections.AsyncListView"
);
private static final Comparator<XValue> ourArrayElementsComparator = (o1, o2) -> {
if (o1 instanceof FlexValue && o2 instanceof FlexValue) {
String name = ((FlexValue)o1).myName;
String name2 = ((FlexValue)o2).myName;
if (!StringUtil.isEmpty(name) &&
!StringUtil.isEmpty(name2) &&
Character.isDigit(name.charAt(0)) &&
Character.isDigit(name2.charAt(0))
) {
try {
return Integer.parseInt(name) - Integer.parseInt(name2);
}
catch (NumberFormatException ignore) {/**/}
}
if (name == null) {
return name2 == null ? 0 : -1;
}
else if (name2 == null) {
return 1;
}
return name.compareToIgnoreCase(name2);
}
return 1;
};
enum ValueType {
This(PlatformIcons.CLASS_ICON),
Parameter(PlatformIcons.PARAMETER_ICON),
Variable(PlatformIcons.VARIABLE_ICON),
Field(PlatformIcons.FIELD_ICON),
ScopeChainEntry(PlatformIcons.CLASS_INITIALIZER),
Other(null);
private @Nullable final Icon myIcon;
ValueType(final @Nullable Icon icon) {
myIcon = icon;
}
}
FlexValue(final FlexStackFrame flexStackFrame,
final FlexDebugProcess flexDebugProcess,
final @Nullable XSourcePosition sourcePosition,
final String name,
final String expression,
final String result,
final @Nullable String parentResult,
final @NotNull ValueType valueType) {
myFlexStackFrame = flexStackFrame;
myDebugProcess = flexDebugProcess;
mySourcePosition = sourcePosition;
myName = name;
myExpression = expression;
myResult = unescape(result, flexDebugProcess.isFlexSdk_4_12plus_IdeMode());
myParentResult = parentResult;
myValueType = valueType;
}
String getResult() {
return myResult;
}
@Override
public String getEvaluationExpression() {
return myExpression;
}
public void setPreferredIcon(final Icon preferredIcon) {
myPreferredIcon = preferredIcon;
}
private Icon getIcon() {
return myPreferredIcon == null ? myValueType.myIcon : myPreferredIcon;
}
@Override
public void computePresentation(@NotNull XValueNode node, @NotNull XValuePlace place) {
final boolean isObject = myResult.contains(OBJECT_MARKER);
String val = myResult;
String typeFromFlexValueResult = null;
String additionalInfo = null;
if (isObject) {
final Pair<String, String> typeAndAdditionalInfo = getTypeAndAdditionalInfo(myResult);
typeFromFlexValueResult = typeAndAdditionalInfo.first;
additionalInfo = typeAndAdditionalInfo.second;
if (typeFromFlexValueResult != null) {
try {
val = "[".concat(getObjectId(myResult, myResult.indexOf(OBJECT_MARKER), OBJECT_MARKER)).concat("]");
}
catch (StringIndexOutOfBoundsException e) {
FlexDebugProcess.log(new Exception(myResult, e));
}
}
}
if ((XML_TYPE.equals(typeFromFlexValueResult) || XMLLIST_TYPE.equals(typeFromFlexValueResult)) && myExpression.indexOf('=') == -1) {
if (myDebugProcess.isDebuggerFromSdk3()) {
if (XMLLIST_TYPE.equals(typeFromFlexValueResult)) {
setXmlListPresentation(node, val, this);
return;
}
else if (additionalInfo != null) {
setXmlPresentation(node, additionalInfo, this);
return;
}
}
else {
scheduleToXmlStringCalculation(node, typeFromFlexValueResult);
// return; no return - show default presentation until toXmlString calculated
}
}
final String type = getType(typeFromFlexValueResult);
if (type != null && isCollection(type)) {
if (type.equals(VECTOR) || type.startsWith(GENERIC_VECTOR_PREFIX)) {
scheduleVectorPresentation(node, typeFromFlexValueResult);
}
else {
scheduleCollectionSizePresentation(node, typeFromFlexValueResult, "");
}
}
val = setFullValueEvaluatorIfNeeded(node, val, false);
node.setPresentation(getIcon(), typeFromFlexValueResult, val, isObject);
}
private static boolean isCollectionWithDirectContent(final String fqn) {
return fqn != null && ArrayUtil.contains(fqn, COLLECTIONS_WITH_DIRECT_CONTENT);
}
private static boolean isCollection(final @NotNull String type) {
return isGenericVector(type) || ArrayUtil.contains(type, COLLECTION_CLASSES);
}
private void scheduleVectorPresentation(final XValueNode node, final String type) {
final FlexStackFrame.EvaluateCommand command =
myFlexStackFrame.new EvaluateCommand(myExpression + ".fixed", new XDebuggerEvaluator.XEvaluationCallback() {
@Override
public void evaluated(@NotNull XValue result) {
if (!node.isObsolete()) {
final String resultText = ((FlexValue)result).myResult;
final String prefix = ("true".equals(resultText) || "false".equals(resultText)) ? "fixed = " + resultText : "";
node.setPresentation(getIcon(), type, prefix, true);
scheduleCollectionSizePresentation(node, type, prefix);
}
}
@Override
public void errorOccurred(@NotNull String errorMessage) {
}
});
myDebugProcess.addPendingCommand(new CompositeDebuggerCommand(node, command), 100);
}
private void scheduleCollectionSizePresentation(final XValueNode node, final String type, final String prefix) {
final FlexStackFrame.EvaluateCommand command =
myFlexStackFrame.new EvaluateCommand(myExpression + ".length", new XDebuggerEvaluator.XEvaluationCallback() {
@Override
public void evaluated(@NotNull XValue result) {
if (!node.isObsolete()) {
final String resultText = ((FlexValue)result).myResult;
final int index = resultText.indexOf(" (0x");
if (index != -1) {
final String value = (prefix.isEmpty() ? "" : prefix + ", ") + "size = " + resultText.substring(0, index);
node.setPresentation(getIcon(), type, value, true);
}
}
}
@Override
public void errorOccurred(@NotNull String errorMessage) {
}
});
myDebugProcess.addPendingCommand(new CompositeDebuggerCommand(node, command), 100);
}
private static void setXmlListPresentation(final XValueNode node, final String value, final FlexValue flexValue) {
node.setFullValueEvaluator(new XFullValueEvaluator() {
@Override
public void startEvaluation(@NotNull XFullValueEvaluationCallback callback) {
new XmlObjectEvaluator(flexValue, callback).startEvaluation();
}
});
node.setPresentation(flexValue.getIcon(), XMLLIST_TYPE, value, true);
}
private static void setXmlPresentation(final XValueNode node, final String additionalInfo, final FlexValue flexValue) {
/*
additionalInfo may look like following:
"text element content"
"element <root attr=\"attrValue\">"
"element <child/>"
*/
final boolean isElement = additionalInfo.startsWith(ELEMENT_MARKER + "<") && additionalInfo.endsWith(">");
final boolean isEmptyElement = isElement && additionalInfo.endsWith("/>");
final boolean isText = !isElement && additionalInfo.startsWith(TEXT_MARKER);
if (isText || isElement) {
String textToShow;
if (isText) {
textToShow = additionalInfo.substring(TEXT_MARKER.length());
}
else if (isEmptyElement) {
textToShow = additionalInfo.substring(ELEMENT_MARKER.length());
}
else {
final String startTag = additionalInfo.substring(ELEMENT_MARKER.length());
final int spaceIndex = startTag.indexOf(" ");
final String tagName = startTag.substring(1, spaceIndex > 0 ? spaceIndex : startTag.length() - 1);
textToShow = startTag + "..." + "</" + tagName + "> ";
if (textToShow.length() > XValueNode.MAX_VALUE_LENGTH) {
textToShow = textToShow.substring(0, XValueNode.MAX_VALUE_LENGTH);
}
node.setFullValueEvaluator(new XFullValueEvaluator() {
@Override
public void startEvaluation(@NotNull XFullValueEvaluationCallback callback) {
new XmlObjectEvaluator(flexValue, callback).startEvaluation();
}
});
node.setPresentation(flexValue.getIcon(), XML_TYPE, textToShow, true);
return;
}
textToShow = setFullValueEvaluatorIfNeeded(node, textToShow, true);
node.setPresentation(flexValue.getIcon(), XML_TYPE, textToShow, true);
return;
}
node.setPresentation(flexValue.getIcon(), XML_TYPE, setFullValueEvaluatorIfNeeded(node, additionalInfo, true), true);
}
private void scheduleToXmlStringCalculation(final XValueNode node, final String type) {
final FlexStackFrame.EvaluateCommand
command = myFlexStackFrame.new EvaluateCommand(myExpression + ".toXMLString()", new XDebuggerEvaluator.XEvaluationCallback() {
@Override
public void evaluated(@NotNull XValue result) {
setResult(((FlexValue)result).myResult, node, type, true);
}
@Override
public void errorOccurred(@NotNull String errorMessage) {
setResult(errorMessage, node, type, true);
}
private void setResult(String value, XValueNode node, String type, boolean hasChildren) {
if (!node.isObsolete()) {
value = setFullValueEvaluatorIfNeeded(node, value, true);
node.setPresentation(getIcon(), type, value, hasChildren);
}
}
});
myDebugProcess.addPendingCommand(new CompositeDebuggerCommand(node, command), 700);
}
private static String setFullValueEvaluatorIfNeeded(final XValueNode node, String value, final boolean isXml) {
final String fullValue = value;
final int lfIndex = fullValue.indexOf('\n');
final int crIndex = fullValue.indexOf('\r');
if (fullValue.length() > XValueNode.MAX_VALUE_LENGTH ||
lfIndex > -1 && lfIndex < fullValue.length() - 1 ||
crIndex > -1 && crIndex < fullValue.length() - 1) {
final boolean quoted = fullValue.charAt(0) == '\'' && fullValue.charAt(fullValue.length() - 1) == '\'';
final boolean doubleQuoted = fullValue.charAt(0) == '\"' && fullValue.charAt(fullValue.length() - 1) == '\"';
if (value.length() > XValueNode.MAX_VALUE_LENGTH) {
final String ending = doubleQuoted ? "\" " : quoted ? "\' " : " ";
value = value.substring(0, XValueNode.MAX_VALUE_LENGTH).concat(ending);
}
value = value.trim();
final String unquoted = quoted || doubleQuoted ? fullValue.substring(1, fullValue.length() - 1) : fullValue;
node.setFullValueEvaluator(
new XFullValueEvaluator() {
@Override
public void startEvaluation(@NotNull XFullValueEvaluationCallback callback) {
callback.evaluated(StringUtil.convertLineSeparators(unquoted), isXml ? XmlObjectEvaluator.MONOSPACED_FONT : null);
}
});
}
return value;
}
@Override
public XValueModifier getModifier() {
return new XValueModifier() {
@Override
public void setValue(@NotNull String _expression, @NotNull final XModificationCallback callback) {
FlexStackFrame.EvaluateCommand command = myFlexStackFrame.new EvaluateCommand(myExpression + "=" + _expression, null) {
@Override
protected void dispatchResult(String s) {
super.dispatchResult(s);
callback.valueModified();
}
};
myDebugProcess.sendCommand(command);
}
};
}
@Override
public void computeChildren(@NotNull final XCompositeNode node) {
final int i = myResult.indexOf(OBJECT_MARKER);
if (i == -1) super.computeChildren(node);
final String typeFromFlexValueResult = getTypeAndAdditionalInfo(myResult).first;
final String expression;
try {
expression = referenceObjectBase(i, OBJECT_MARKER);
}
catch (StringIndexOutOfBoundsException e) {
FlexDebugProcess.log(new Exception(myResult, e));
node.addChildren(XValueChildrenList.EMPTY, true);
return;
}
final FlexStackFrame.EvaluateCommand
command = myFlexStackFrame.new EvaluateCommand(expression, null) {
@Override
CommandOutputProcessingMode doOnTextAvailable(@NonNls final String resultS) {
StringTokenizer tokenizer = new StringTokenizer(resultS, "\r\n");
// skip first token; it contains $-prefix followed by myResult: $6 = [Object 30860193, class='__AS3__.vec::Vector.<String>']
tokenizer.nextToken();
final LinkedHashMap<String, FlexValue> fieldNameToFlexValueMap = new LinkedHashMap<>(tokenizer.countTokens());
final NodeClassInfo nodeClassInfo =
DumbService.getInstance(myDebugProcess.getSession().getProject()).runReadActionInSmartMode(() -> {
final Project project = myDebugProcess.getSession().getProject();
final JSClass jsClass = mySourcePosition == null
? null
: findJSClass(project,
ModuleUtilCore.findModuleForFile(mySourcePosition.getFile(), project),
typeFromFlexValueResult);
return jsClass == null ? null : NodeClassInfo.getNodeClassInfo(jsClass);
});
while (tokenizer.hasMoreElements()) {
final String s = tokenizer.nextToken().trim();
if (s.length() == 0) continue;
final int delimIndex = s.indexOf(FlexStackFrame.DELIM);
if (delimIndex == -1) {
FlexDebugProcess.log("Unrecognized string:" + s);
continue;
}
final String fieldName = s.substring(0, delimIndex);
final String result = s.substring(delimIndex + FlexStackFrame.DELIM.length());
if (result.startsWith("[Setter ")) {
// such values do not give any useful information:
// [Setter 62, name='Child@3d613bb::staticSetter']
// [Setter 78]
continue;
}
String evaluatedPath = myExpression;
if (fieldName.length() > 0 && Character.isDigit(fieldName.charAt(0))) {
evaluatedPath += "[\"" + fieldName + "\"]";
}
else {
evaluatedPath += "." + fieldName;
}
// either parameter of static function from scopechain or a field. Static functions from scopechain look like following:
// // [Object 52571545, class='Main$/staticFunction']
final ValueType valueType =
typeFromFlexValueResult != null && typeFromFlexValueResult.indexOf('/') > -1 ? ValueType.Parameter : ValueType.Field;
final FlexValue flexValue =
new FlexValue(myFlexStackFrame, myDebugProcess, mySourcePosition, fieldName, evaluatedPath, result, FlexValue.this.myResult,
valueType);
addValueCheckingDuplicates(flexValue, fieldNameToFlexValueMap);
}
addChildren(node, fieldNameToFlexValueMap, nodeClassInfo);
return CommandOutputProcessingMode.DONE;
}
};
myDebugProcess.sendCommand(command);
}
public boolean canNavigateToTypeSource() {
final boolean isObject = myResult.contains(OBJECT_MARKER);
if (isObject && mySourcePosition != null) {
final Pair<String, String> typeAndAdditionalInfo = getTypeAndAdditionalInfo(myResult);
final String typeFromFlexValueResult = typeAndAdditionalInfo.first;
return typeFromFlexValueResult != null;
}
return false;
}
public void computeTypeSourcePosition(@NotNull final XNavigatable navigatable) {
if (mySourcePosition == null) {
navigatable.setSourcePosition(null);
return;
}
final boolean isObject = myResult.contains(OBJECT_MARKER);
if (isObject) {
final Pair<String, String> typeAndAdditionalInfo = getTypeAndAdditionalInfo(myResult);
final String typeFromFlexValueResult = typeAndAdditionalInfo.first;
if (typeFromFlexValueResult != null) {
final Project project = myDebugProcess.getSession().getProject();
final JSClass jsClass =
findJSClass(project, ModuleUtilCore.findModuleForFile(mySourcePosition.getFile(), project), typeFromFlexValueResult);
navigatable.setSourcePosition(DebuggerSupportUtils.calcSourcePosition(jsClass));
return;
}
}
navigatable.setSourcePosition(null);
}
public boolean canNavigateToSource() {
return mySourcePosition != null && (myValueType == ValueType.Variable ||
myValueType == ValueType.Parameter ||
myValueType == ValueType.Field && myParentResult != null);
}
@Override
public void computeSourcePosition(@NotNull final XNavigatable navigatable) {
if (mySourcePosition == null) {
navigatable.setSourcePosition(null);
return;
}
XSourcePosition result = null;
final Project project = myDebugProcess.getSession().getProject();
if (myValueType == ValueType.Variable) {
final PsiElement contextElement =
XDebuggerUtil.getInstance().findContextElement(mySourcePosition.getFile(), mySourcePosition.getOffset(), project, true);
final JSFunction jsFunction = PsiTreeUtil.getParentOfType(contextElement, JSFunction.class);
if (jsFunction != null) {
final Ref<JSVariable> varRef = new Ref<>();
jsFunction.accept(new JSElementVisitor() {
@Override
public void visitJSElement(final JSElement node) {
if (varRef.isNull()) {
node.acceptChildren(this);
}
}
@Override
public void visitJSVariable(final JSVariable node) {
if (myName.equals(node.getName())) {
varRef.set(node);
}
super.visitJSVariable(node);
}
});
if (!varRef.isNull()) {
result = DebuggerSupportUtils.calcSourcePosition(varRef.get());
}
}
}
else if (myValueType == ValueType.Parameter) {
final PsiElement contextElement =
XDebuggerUtil.getInstance().findContextElement(mySourcePosition.getFile(), mySourcePosition.getOffset(), project, true);
final JSFunction jsFunction = PsiTreeUtil.getParentOfType(contextElement, JSFunction.class);
final JSParameter[] parameters = jsFunction == null ? JSParameter.EMPTY_ARRAY : jsFunction.getParameterVariables();
for (final JSParameter parameter : parameters) {
if (myName.equals(parameter.getName())) {
result = DebuggerSupportUtils.calcSourcePosition(parameter);
break;
}
}
}
else if (myValueType == ValueType.Field && myParentResult != null) {
final String typeFromFlexValueResult = getTypeAndAdditionalInfo(myParentResult).first;
final JSClass jsClass =
findJSClass(project, ModuleUtilCore.findModuleForFile(mySourcePosition.getFile(), project), typeFromFlexValueResult);
if (jsClass != null) {
final Ref<PsiElement> fieldRef = new Ref<>();
fieldRef.set(JSInheritanceUtil.findMember(myName, jsClass, JSInheritanceUtil.SearchedMemberType.FieldsAndMethods,
JSFunction.FunctionKind.GETTER, true));
if (fieldRef.isNull() && jsClass instanceof MxmlJSClass) {
final PsiFile file = jsClass.getContainingFile();
final XmlFile xmlFile = file instanceof XmlFile ? (XmlFile)file : null;
final XmlTag rootTag = xmlFile == null ? null : xmlFile.getRootTag();
if (rootTag != null) {
NodeClassInfo.processSubtagsRecursively(rootTag, tag -> {
final XmlAttribute idAttr = tag.getAttribute("id");
final String id = idAttr == null ? null : idAttr.getValue();
if (id != null && id.equals(myName)) {
fieldRef.set(idAttr);
}
return !MxmlJSClass.isTagThatAllowsAnyXmlContent(tag);
});
}
}
result = DebuggerSupportUtils.calcSourcePosition(fieldRef.get());
}
}
navigatable.setSourcePosition(result);
}
private static void addValueCheckingDuplicates(final FlexValue flexValue,
final LinkedHashMap<String, FlexValue> fieldNameToFlexValueMap) {
final String name = flexValue.myName;
FlexValue existingValue;
if ((existingValue = fieldNameToFlexValueMap.get("_" + name)) != null &&
existingValue.getResult().equals(flexValue.getResult())) {
fieldNameToFlexValueMap.remove("_" + name);
}
else if (name.startsWith("_") &&
name.length() > 1 &&
(existingValue = fieldNameToFlexValueMap.get(name.substring(1))) != null &&
existingValue.getResult().equals(flexValue.getResult())) {
return;
}
fieldNameToFlexValueMap.put(name, flexValue);
}
private static void addChildren(final XCompositeNode node,
final LinkedHashMap<String, FlexValue> fieldNameToFlexValueMap,
final @Nullable NodeClassInfo nodeClassInfo) {
final List<FlexValue> elementsOfCollection = new LinkedList<>();
final XValueChildrenList ownStaticFields = new XValueChildrenList();
final XValueChildrenList ownStaticProperties = new XValueChildrenList();
final XValueChildrenList ownFields = new XValueChildrenList();
final XValueChildrenList ownProperties = new XValueChildrenList();
final XValueChildrenList inheritedStaticFields = new XValueChildrenList();
final XValueChildrenList inheritedStaticProperties = new XValueChildrenList();
final XValueChildrenList inheritedFields = new XValueChildrenList();
final XValueChildrenList inheritedProperties = new XValueChildrenList();
for (final Map.Entry<String, FlexValue> entry : fieldNameToFlexValueMap.entrySet()) {
final String name = entry.getKey();
final FlexValue flexValue = entry.getValue();
if (isInteger(name)) {
elementsOfCollection.add(flexValue);
continue;
}
if (nodeClassInfo == null) {
ownFields.add(name, flexValue);
}
else {
if (updateIconAndAddToListIfMatches(name, flexValue, nodeClassInfo.myOwnStaticFields, ownStaticFields) ||
updateIconAndAddToListIfMatches(name, flexValue, nodeClassInfo.myOwnStaticProperties, ownStaticProperties) ||
updateIconAndAddToListIfMatches(name, flexValue, nodeClassInfo.myOwnFields, ownFields) ||
updateIconAndAddToListIfMatches(name, flexValue, nodeClassInfo.myOwnProperties, ownProperties) ||
updateIconAndAddToListIfMatches(name, flexValue, nodeClassInfo.myInheritedStaticFields, inheritedStaticFields) ||
updateIconAndAddToListIfMatches(name, flexValue, nodeClassInfo.myInheritedStaticProperties, inheritedStaticProperties) ||
updateIconAndAddToListIfMatches(name, flexValue, nodeClassInfo.myInheritedFields, inheritedFields) ||
updateIconAndAddToListIfMatches(name, flexValue, nodeClassInfo.myInheritedProperties, inheritedProperties)) {
continue;
}
(nodeClassInfo.myIsDynamic ? ownFields : inheritedFields).add(name, flexValue);
}
}
Collections.sort(elementsOfCollection, ourArrayElementsComparator);
XValueChildrenList inheritedNodeSingletonList = XValueChildrenList.EMPTY;
if (inheritedStaticFields.size() + inheritedStaticProperties.size() + inheritedFields.size() + inheritedProperties.size() > 0) {
if (inheritedStaticFields.size() + inheritedStaticProperties.size() > 0) {
final XValueChildrenList inheritedStatics =
createWrappingGroupList("Static members", inheritedStaticFields, inheritedStaticProperties);
inheritedNodeSingletonList = createWrappingGroupList("Inherited members", inheritedStatics, inheritedFields, inheritedProperties);
}
else {
inheritedNodeSingletonList = createWrappingGroupList("Inherited members", inheritedStaticFields, inheritedStaticProperties,
inheritedFields, inheritedProperties);
}
}
if (nodeClassInfo != null && isCollectionWithDirectContent(nodeClassInfo.myFqn)) {
final XValueChildrenList fieldsAndPropertiesSingletonList =
createWrappingGroupList("Fields and properties", inheritedNodeSingletonList, ownStaticFields, ownStaticProperties, ownFields,
ownProperties);
node.addChildren(fieldsAndPropertiesSingletonList, false);
}
else {
node.addChildren(inheritedNodeSingletonList, false);
if (ownStaticFields.size() + ownStaticProperties.size() > 0) {
node.addChildren(createWrappingGroupList("Static members", ownStaticFields, ownStaticProperties), false);
}
node.addChildren(ownFields, false);
node.addChildren(ownProperties, false);
}
final XValueChildrenList elementsOfCollectionList = new XValueChildrenList();
for (final FlexValue flexValue : elementsOfCollection) {
elementsOfCollectionList.add(flexValue.myName, flexValue);
}
node.addChildren(elementsOfCollectionList, true);
}
private static XValueChildrenList createWrappingGroupList(final String groupName, final XValueChildrenList... listsToWrap) {
final XValueGroup group = new XValueGroup(groupName) {
@Override
public void computeChildren(@NotNull final XCompositeNode node) {
for (final XValueChildrenList list : listsToWrap) {
node.addChildren(list, false);
}
node.addChildren(XValueChildrenList.EMPTY, true);
}
};
final XValueChildrenList inheritedSingleNodeList = new XValueChildrenList();
inheritedSingleNodeList.addTopGroup(group);
return inheritedSingleNodeList;
}
private static boolean updateIconAndAddToListIfMatches(final String name,
final FlexValue flexValue,
final Map<String, Icon> nameToIconMap,
final XValueChildrenList list) {
final Icon icon = nameToIconMap.get(name);
if (icon != null) {
flexValue.setPreferredIcon(icon);
list.add(flexValue.myName, flexValue);
return true;
}
return false;
}
private static boolean isInteger(final String s) {
try {
//noinspection ResultOfMethodCallIgnored
Integer.parseInt(s);
return true;
}
catch (NumberFormatException ignored) {
return false;
}
}
private String referenceObjectBase(int i, String marker) {
// expression may have incorrect syntax like x.dict1.-1. (see examples in http://youtrack.jetbrains.net/issue/IDEA-56653)
// so it is more reliable to use objectId
//if (myDebugProcess.isDebuggerFromSdk4()) {
// return expression + ".";
//}
return "#" + getObjectId(myResult, i, marker) + ".";
}
private static String getObjectId(String result, int i, String marker) {
String s = result.substring(i + marker.length(), result.indexOf(','));
return FlexStackFrame.validObjectId(s);
}
/**
* Examples of type (<code>getTypeAndAdditionalInfo().first</code>) :
* <ul>
* <li><code>null</code></li>
* <li><code>flash.events::MouseEvent</code></li>
* <li><code>Main$/staticFunction</code></li>
* <li><code>XML</code></li>
* <li><code>pack.SomeClass$</code></li>
* <li><code>Vector.<String></String></code></li>
* </ul>
*/
private static Pair<String, String> getTypeAndAdditionalInfo(final @Nullable String fdbText) {
if (fdbText == null) return Pair.create(null, null);
// [Object 52571545, class='flash.events::MouseEvent']
// [Object 52571545, class='Main$/staticFunction']
// [Object 62823129, class='XML@3be9ad9 element <abc/>']
String type = null;
String additionalInfo = null;
final int classIndex = fdbText.indexOf(FlexStackFrame.CLASS_MARKER);
final int lastQuoteIndex = fdbText.lastIndexOf("'");
if (classIndex != -1 && lastQuoteIndex > classIndex) {
int typeStart = classIndex + FlexStackFrame.CLASS_MARKER.length();
final String inQuotes = fdbText.substring(typeStart, lastQuoteIndex);
final int atIndex = inQuotes.indexOf("@");
if (atIndex > 0) {
type = inQuotes.substring(0, atIndex);
final int spaceIndex = inQuotes.indexOf(" ", atIndex);
if (spaceIndex != -1) {
additionalInfo = inQuotes.substring(spaceIndex, inQuotes.length());
}
}
else {
type = inQuotes;
}
}
if ("[]".equals(type)) {
type = "Array";
}
if (type != null) {
type = StringUtil.replace(type, VECTOR_PREFIX, "");
}
return Pair.create(type, additionalInfo);
}
/**
* Returned result can contain extra <b>$</b> after real class FQN in case of static context. For example <code>pack.Main$</code>
* Also it may contain vector type, e.g. <code>Vector.<int></code>
*/
@Nullable
private static String getType(final String typeFromFlexValueResult) {
if (typeFromFlexValueResult != null && !typeFromFlexValueResult.contains("/")) {
return typeFromFlexValueResult.replace("::", ".");
}
return null;
}
private static boolean isGenericVector(final String type) {
return type.startsWith(GENERIC_VECTOR_PREFIX);
}
@Nullable
private static JSClass findJSClass(final Project project, final @Nullable Module module, final String typeFromFlexValueResult) {
String type = getType(typeFromFlexValueResult);
if (type != null) {
if (isGenericVector(type)) {
type = VECTOR;
}
final JavaScriptIndex jsIndex = JavaScriptIndex.getInstance(project);
PsiElement jsClass = ActionScriptClassResolver.findClassByQName(type, jsIndex, module);
if (!(jsClass instanceof JSClass) && type.endsWith("$")) { // fdb adds '$' to class name in case of static context
jsClass = ActionScriptClassResolver.findClassByQName(type.substring(0, type.length() - 1), jsIndex, module);
}
if (!(jsClass instanceof JSClass) && module != null) {
// probably this class came from dynamically loaded module that is not in moduleWithDependenciesAndLibrariesScope(module)
final GlobalSearchScope scope = ProjectScope.getAllScope(project);
jsClass = ActionScriptClassResolver.findClassByQNameStatic(type, scope);
if (!(jsClass instanceof JSClass) && type.endsWith("$")) {
jsClass = ActionScriptClassResolver.findClassByQNameStatic(type.substring(0, type.length() - 1), scope);
}
}
return jsClass instanceof JSClass ? (JSClass)jsClass : null;
}
return null;
}
/**
* Looks for IDEA-ESCAPE-START and IDEA-ESCAPE-END markers in input string and unescapes symbols inside these markers. Markers are removed.
*/
private static String unescape(String str, final boolean flexSdk_4_12plus_IdeMode) {
if (flexSdk_4_12plus_IdeMode) {
return StringUtil.unescapeStringCharacters(str);
}
int escapeEndIndex = 0;
int escapeStartIndex;
while ((escapeStartIndex = str.indexOf(ESCAPE_START, escapeEndIndex - ESCAPE_START.length())) > -1) {
escapeEndIndex = str.indexOf(ESCAPE_END, escapeStartIndex);
if (escapeEndIndex < 0) {
escapeEndIndex = str.length();
}
str = str.substring(0, escapeStartIndex) +
StringUtil.unescapeStringCharacters(str.substring(escapeStartIndex + ESCAPE_START.length(), escapeEndIndex)) +
(escapeEndIndex + ESCAPE_END.length() <= str.length() ? str.substring(escapeEndIndex + ESCAPE_END.length()) : "");
}
return str;
}
}