package com.intellij.flex.uiDesigner.mxml; import com.intellij.flex.uiDesigner.InvalidPropertyException; import com.intellij.flex.uiDesigner.io.Amf3Types; import com.intellij.flex.uiDesigner.io.PrimitiveAmfOutputStream; import com.intellij.injected.editor.VirtualFileWindow; import com.intellij.lang.javascript.JSTokenTypes; import com.intellij.lang.javascript.psi.*; import com.intellij.lang.javascript.psi.ecmal4.JSClass; import com.intellij.openapi.application.AccessToken; import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.tree.IElementType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; class ExpressionBinding extends Binding { private final JSElement expression; public ExpressionBinding(JSElement expression) { this.expression = expression; } @Override protected int getType() { return BindingType.EXPRESSION; } @Override void write(PrimitiveAmfOutputStream out, BaseWriter writer, ValueReferenceResolver valueReferenceResolver) throws InvalidPropertyException { super.write(out, writer, valueReferenceResolver); // chain (not null expression.getQualifier()) supported only for binding from MXML, not from variable initializer value if (expression instanceof JSReferenceExpression) { writeReferenceExpression((JSReferenceExpression)expression, out, writer, valueReferenceResolver, true); } else if (expression instanceof JSFunction) { writeFunction((JSFunction)expression, writer, valueReferenceResolver, true); } else { writeExpression((JSExpression)expression, out, writer, valueReferenceResolver); } } private static void writeExpression(JSExpression expression, PrimitiveAmfOutputStream out, BaseWriter writer, @Nullable ValueReferenceResolver valueReferenceResolver) throws InvalidPropertyException { if (expression instanceof JSLiteralExpression) { writeLiteralExpression((JSLiteralExpression)expression, out, writer); } else if (expression instanceof JSObjectLiteralExpression) { writeObjectExpression((JSObjectLiteralExpression)expression, out, writer, valueReferenceResolver); } else if (expression instanceof JSArrayLiteralExpression) { writeArrayLiteralExpression((JSArrayLiteralExpression)expression, out, writer, valueReferenceResolver); } else if (expression instanceof JSNewExpression) { writeCallExpression((JSNewExpression)expression, out, writer, valueReferenceResolver); } else if (expression instanceof JSReferenceExpression) { writeReferenceExpression((JSReferenceExpression)expression, out, writer, valueReferenceResolver, false); } else if (expression instanceof JSCallExpression) { writeCallExpression((JSCallExpression)expression, out, writer, valueReferenceResolver); } else { throw new UnsupportedOperationException(expression.getText()); } } private static void writeCallExpression(JSCallExpression expression, PrimitiveAmfOutputStream out, BaseWriter writer, ValueReferenceResolver valueReferenceResolver) throws InvalidPropertyException { final JSReferenceExpression methodExpression = (JSReferenceExpression)expression.getMethodExpression(); final PsiElement psiElement = resolveReferenceExpression(methodExpression, true); final JSExpression[] arguments; if (psiElement instanceof JSClass) { // IDEA-74060, {Number('20')} arguments = expression.getArguments(); assert arguments.length == 1; writeExpression(arguments[0], out, writer, valueReferenceResolver); return; } else if (psiElement instanceof JSReferenceExpression) { writeReferenceExpression((JSReferenceExpression)psiElement, out, writer, valueReferenceResolver, false); return; } writeFunction((JSFunction)psiElement, writer, valueReferenceResolver, false, expression, methodExpression); } private static void writeFunction(JSFunction function, BaseWriter writer, @Nullable ValueReferenceResolver valueReferenceResolver, boolean isBinding) throws InvalidPropertyException { final PsiFile psiFile = function.getContainingFile(); if (psiFile instanceof JSFile) { VirtualFile file = psiFile.getViewProvider().getVirtualFile(); if (file instanceof VirtualFileWindow) { if (ProjectRootManager.getInstance(psiFile.getProject()).getFileIndex() .isInSourceContent(((VirtualFileWindow)file).getDelegate())) { throw new UnsupportedOperationException(function.getText()); } } } writeFunction(function, writer, valueReferenceResolver, isBinding, null, null); } private static void writeFunction(JSFunction function, BaseWriter writer, @Nullable ValueReferenceResolver valueReferenceResolver, boolean isBinding, @Nullable JSCallExpression expression, @Nullable JSReferenceExpression methodExpression) throws InvalidPropertyException { final PrimitiveAmfOutputStream out = writer.getOut(); JSExpression[] arguments; final int rollbackPosition; final int start; if (function.isConstructor()) { assert expression != null; arguments = expression.getArguments(); final JSClass jsClass = (JSClass)function.getParent(); // text="{new String('newString')}" writer.newInstance(jsClass.getQualifiedName(), arguments.length, true); rollbackPosition = out.allocateShort(); start = out.size(); } else { out.write(ExpressionMessageTypes.CALL); rollbackPosition = out.allocateShort(); start = out.size(); // text="{resourceManager.getString('core', 'viewSource')}" if (methodExpression != null) { JSReferenceExpression qualifier = (JSReferenceExpression)methodExpression.getQualifier(); while (qualifier != null) { writer.classOrPropertyName(qualifier.getReferencedName()); qualifier = (JSReferenceExpression)qualifier.getQualifier(); } } out.write(0); writer.classOrPropertyName(function.getName()); if (function.isGetProperty()) { out.write(isBinding ? -2 : -1); return; } else { assert expression != null; arguments = expression.getArguments(); out.write(arguments.length); } } for (JSExpression argument : arguments) { writeExpression(argument, out, writer, valueReferenceResolver); } out.putShort(out.size() - start, rollbackPosition); } static void writeArrayLiteralExpression(JSArrayLiteralExpression expression, PrimitiveAmfOutputStream out, BaseWriter writer, @Nullable ValueReferenceResolver valueReferenceResolver) throws InvalidPropertyException { JSExpression[] expressions = expression.getExpressions(); writer.arrayHeader(expressions.length); for (JSExpression item : expressions) { writeExpression(item, out, writer, valueReferenceResolver); } } @NotNull private static PsiElement resolveReferenceExpression(JSReferenceExpression expression, boolean qualificatorSupported) throws InvalidPropertyException { if (!qualificatorSupported) { checkQualifier(expression); } final AccessToken token = ReadAction.start(); final PsiElement element; try { element = expression.resolve(); } finally { token.finish(); } if (element == null) { throw new InvalidPropertyException(expression, "unresolved.variable.or.type", expression.getReferencedName()); } return element; } private static void checkQualifier(JSReferenceExpression expression) { JSExpression qualifier = expression.getQualifier(); if (qualifier != null && !(qualifier instanceof JSThisExpression)) { throw new UnsupportedOperationException(expression.getText()); } } private static void writeReferenceExpression(JSReferenceExpression expression, PrimitiveAmfOutputStream out, BaseWriter writer, ValueReferenceResolver valueReferenceResolver, boolean qualificatorSupportedForMxmlBinding) throws InvalidPropertyException { final PsiElement element; List<String> qualifiers = null; JSExpression expressionQualifier = expression.getQualifier(); JSReferenceExpression qualifier; if (expressionQualifier instanceof JSReferenceExpression) { qualifier = (JSReferenceExpression)expressionQualifier; } else if (expressionQualifier instanceof JSIndexedPropertyAccessExpression) { qualifier = (JSReferenceExpression)((JSIndexedPropertyAccessExpression)expressionQualifier).getQualifier(); } else if (expressionQualifier != null && !(expressionQualifier instanceof JSThisExpression)) { // we can skip "this." throw new UnsupportedOperationException("unknown qualifier " + expressionQualifier.toString() + " " + expression.getText()); } else { qualifier = null; } if (qualificatorSupportedForMxmlBinding && qualifier != null) { JSReferenceExpression topElement; qualifiers = new ArrayList<>(); do { qualifiers.add(qualifier.getReferencedName()); topElement = qualifier; } while ((qualifier = (JSReferenceExpression)qualifier.getQualifier()) != null); element = resolveReferenceExpression(topElement, true); } else { element = resolveReferenceExpression(expression, false); } if (element instanceof JSClass) { // {VerticalAlign} if (qualifiers == null) { writer.classReference(((JSClass)element).getQualifiedName()); } else { // check for {VerticalAlign.MIDDLE} PsiElement possibleVariable = resolveReferenceExpression(expression, true); if (possibleVariable instanceof JSVariable) { JSVariable variable = (JSVariable)possibleVariable; if (variable.isConst()) { JSExpression initializer = ((JSVariable)possibleVariable).getInitializer(); if (initializer != null) { writeExpression(initializer, out, writer, valueReferenceResolver); return; } else { throw new InvalidPropertyException(expression, "const.without.initializer", expression.getText()); } } } throw new UnsupportedOperationException(expression.getText()); } } else if (element instanceof JSVariable) { checkQualifier(expression); VariableReference valueReference = valueReferenceResolver.getNullableValueReference((JSVariable)element); if (valueReference != null) { out.write(ExpressionMessageTypes.VARIABLE_REFERENCE); // may be already referenced, i.e. VariableReference created for this variable valueReference.write(out, writer, valueReferenceResolver); } else { writeJSVariable(((JSVariable)element), out, writer, valueReferenceResolver); } } else if (element instanceof JSFunction) { writeFunction((JSFunction)element, writer, valueReferenceResolver, true); } else { final String hostObjectId; if (qualifiers == null) { out.write(ExpressionMessageTypes.MXML_OBJECT_REFERENCE); hostObjectId = expression.getReferencedName(); } else { out.write(ExpressionMessageTypes.MXML_OBJECT_CHAIN); writer.classOrPropertyName(expression.getReferencedName()); for (int i = qualifiers.size() - 2 /* last qualifier is not included to chain, it is host object */; i >= 0; i--) { writer.classOrPropertyName(qualifiers.get(i)); } writer.endObject(); hostObjectId = qualifiers.get(qualifiers.size() - 1); } try { valueReferenceResolver.getValueReference(hostObjectId).write(out, writer, valueReferenceResolver); } catch (IllegalStateException ignored) { // getValueReference throws IllegalStateException if value reference is null throw new UnsupportedOperationException(expression.getText()); } } } static void writeJSVariable(JSVariable variable, PrimitiveAmfOutputStream out, BaseWriter writer, ValueReferenceResolver valueReferenceResolver) throws InvalidPropertyException { JSExpression initializer = variable.getInitializer(); if (initializer == null) { MxmlWriter.LOG.warn("Unsupported variable without initializer: " + variable.getParent().getText() + ", write as null"); out.write(Amf3Types.NULL); } else { writeExpression(initializer, out, writer, valueReferenceResolver); } } private static void writeLiteralExpression(JSLiteralExpression expression, PrimitiveAmfOutputStream out, BaseWriter writer) { if (expression.isNumericLiteral()) { out.writeAmfDouble(expression.getText()); } else { final PsiElement firstChild = expression.getFirstChild(); if (firstChild == null) { writer.string(XmlElementValueProvider.EMPTY); return; } final IElementType elementType = firstChild.getNode().getElementType(); if (elementType == JSTokenTypes.TRUE_KEYWORD) { writer.getOut().write(Amf3Types.TRUE); } else if (elementType == JSTokenTypes.FALSE_KEYWORD) { writer.getOut().write(Amf3Types.FALSE); } else if (elementType == JSTokenTypes.NULL_KEYWORD) { writer.getOut().write(Amf3Types.NULL); } else { writer.string(StringUtil.stripQuotesAroundValue(expression.getText())); } } } private static void writeObjectExpression(JSObjectLiteralExpression expression, PrimitiveAmfOutputStream out, BaseWriter writer, ValueReferenceResolver valueReferenceResolver) throws InvalidPropertyException { JSProperty[] properties = expression.getProperties(); out.write(ExpressionMessageTypes.SIMPLE_OBJECT); for (JSProperty property : properties) { writer.classOrPropertyName(property.getName()); writeExpression(property.getValue(), out, writer, valueReferenceResolver); } writer.endObject(); } }