package com.intellij.flex.uiDesigner.mxml; import com.intellij.flex.uiDesigner.FlashUIDesignerBundle; import com.intellij.flex.uiDesigner.InjectionUtil; import com.intellij.flex.uiDesigner.InvalidPropertyException; import com.intellij.flex.uiDesigner.ProblemsHolder; import com.intellij.javascript.flex.FlexAnnotationNames; import com.intellij.lang.javascript.JSTokenTypes; import com.intellij.lang.javascript.psi.*; import com.intellij.lang.javascript.psi.ecmal4.JSAttribute; import com.intellij.lang.javascript.psi.ecmal4.JSAttributeNameValuePair; import com.intellij.lang.javascript.psi.ecmal4.JSClass; import com.intellij.lang.properties.IProperty; import com.intellij.lang.properties.psi.PropertiesFile; 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.PsiFileSystemItem; import com.intellij.psi.PsiLanguageInjectionHost; import com.intellij.psi.PsiLanguageInjectionHost.Shred; import com.intellij.psi.impl.source.tree.LeafPsiElement; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; import static com.intellij.flex.uiDesigner.mxml.MxmlWriter.LOG; class InjectedPsiVisitor implements PsiLanguageInjectionHost.InjectedPsiVisitor { private final PsiElement host; private final @Nullable String expectedType; private boolean visited; private boolean unsupported; private ValueWriter valueWriter; private InvalidPropertyException invalidPropertyException; private Binding binding; private final ProblemsHolder problemsHolder; public InjectedPsiVisitor(PsiElement host, @Nullable String expectedType, ProblemsHolder problemsHolder) { this.host = host; this.expectedType = expectedType; this.problemsHolder = problemsHolder; } public ValueWriter getValueWriter() { return unsupported ? InjectedASWriter.IGNORE : valueWriter; } public Binding getBinding() { return binding; } public InvalidPropertyException getInvalidPropertyException() { return invalidPropertyException; } @Override public void visit(@NotNull PsiFile injectedPsi, @NotNull List<Shred> places) { // todo <s:Label text="{demandSelectedTO.journalist.nom} {demandSelectedTO.journalist.prenom}"/> // will be called 2 - first for {demandSelectedTO.journalist.nom} and second for {demandSelectedTO.journalist.prenom} if (visited) { return; } visited = true; assert places.size() == 1; assert places.get(0).getHost() == host; JSFile jsFile = (JSFile)injectedPsi; JSSourceElement[] statements = jsFile.getStatements(); if (statements.length == 0 && (valueWriter = checkCompilerDirective(jsFile)) != null) { return; } assert statements.length == 1; final JSExpression expr = ((JSExpressionStatement)statements[0]).getExpression(); // <s:Image source="@Embed(source='/Users/develar/Pictures/iChat Ico if (!(expr instanceof JSCallExpression)) { valueWriter = InjectedASWriter.IGNORE; return; } JSCallExpression expression = (JSCallExpression)expr; JSExpression[] arguments = expression.getArguments(); if (arguments.length != 1) { logUnsupported(); return; } if (arguments[0] instanceof JSArrayLiteralExpression) { if (isUnexpected(JSCommonTypeNames.ARRAY_CLASS_NAME)) { return; } JSArrayLiteralExpression arrayLiteralExpression = (JSArrayLiteralExpression)arguments[0]; JSExpression[] expressions = arrayLiteralExpression.getExpressions(); if (expressions.length == 0) { valueWriter = InjectedASWriter.IGNORE; return; } // create ExpressionBinding only if expressions contains non-primitive values for (JSExpression itemExpression : expressions) { if (!(itemExpression instanceof JSLiteralExpression)) { binding = new ExpressionBinding(arrayLiteralExpression); return; } } valueWriter = new InjectedArrayOfPrimitivesWriter(arrayLiteralExpression); } else if (arguments[0] instanceof JSReferenceExpression && arguments[0].getChildren().length == 0) { // if propertyName="{CustomSkin}", so, write class, otherwise, it is binding JSReferenceExpression referenceExpression = (JSReferenceExpression)arguments[0]; PsiElement element = referenceExpression.resolve(); if (element == null) { invalidPropertyException = new InvalidPropertyException(expression, "unresolved.variable.or.type", referenceExpression.getReferencedName()); } else if (element instanceof JSClass) { if (isExpectedObjectOrAnyType() || AsCommonTypeNames.CLASS.equals(expectedType)) { valueWriter = new ClassValueWriter(((JSClass)element)); } else { invalidPropertyException = new InvalidPropertyException(expression, "implicit.coercion", "Class", expectedType); } } else if (element instanceof JSVariable) { binding = new VariableBinding(((JSVariable)element)); } else if (element instanceof JSFunction) { binding = new ExpressionBinding(((JSFunction)element)); } else { binding = new MxmlObjectBinding(referenceExpression.getReferencedName(), JSCommonTypeNames.ARRAY_CLASS_NAME.equals(expectedType)); } } else { binding = new ExpressionBinding(arguments[0]); } } private ValueWriter checkCompilerDirective(JSFile jsFile) { PsiElement firstChild = jsFile.getFirstChild(); if (firstChild instanceof LeafPsiElement && ((LeafPsiElement)firstChild).getElementType() == JSTokenTypes.AT) { JSAttribute attribute = (JSAttribute)firstChild.getNextSibling(); assert attribute != null; final String attributeName = attribute.getName(); if (FlexAnnotationNames.EMBED.equals(attributeName)) { return processEmbedDirective(attribute); } else if (FlexAnnotationNames.RESOURCE.equals(attributeName)) { return processResourceDirective(attribute); } else { if (!StringUtil.isEmpty(attributeName)) { problemsHolder.add(host, FlashUIDesignerBundle.message("unsupported.compiler.directive", host.getText())); } return InjectedASWriter.IGNORE; } } return null; } private ValueWriter processResourceDirective(JSAttribute attribute) { String key = null; PropertiesFile bundle = null; for (JSAttributeNameValuePair p : attribute.getValues()) { final String name = p.getName(); if ("key".equals(name)) { key = p.getSimpleValue(); } else if ("bundle".equals(name)) { try { // IDEA-74868 final PsiFileSystemItem referencedPsiFile = InjectionUtil.getReferencedPsiFile(p); if (referencedPsiFile instanceof PropertiesFile) { bundle = (PropertiesFile)referencedPsiFile; } else { LOG.warn("skip resource directive, referenced file is not properties file " + host.getText()); } } catch (InvalidPropertyException e) { invalidPropertyException = e; return InjectedASWriter.IGNORE; } } } if (key == null || key.isEmpty() || bundle == null) { LOG.warn("skip resource directive, one of the required attributes is missed " + host.getText()); return InjectedASWriter.IGNORE; } final IProperty property = bundle.findPropertyByKey(key); if (property == null) { LOG.warn("skip resource directive, key not found " + host.getText()); return InjectedASWriter.IGNORE; } return new ResourceDirectiveValueWriter(property.getUnescapedValue()); } private ValueWriter processEmbedDirective(JSAttribute attribute) { VirtualFile source = null; String mimeType = null; String symbol = null; for (JSAttributeNameValuePair p : attribute.getValues()) { final String name = p.getName(); if (name == null || name.equals("source")) { try { source = InjectionUtil.getReferencedFile(p); } catch (InvalidPropertyException e) { problemsHolder.add(e); return InjectedASWriter.IGNORE; } } else if (name.equals("mimeType")) { mimeType = p.getSimpleValue(); } else if (name.equals("symbol")) { symbol = p.getSimpleValue(); } } if (source == null) { problemsHolder.add(host, FlashUIDesignerBundle.message("embed.source.not.specified", host.getText())); return InjectedASWriter.IGNORE; } if (InjectionUtil.isSwf(source, mimeType)) { return new SwfValueWriter(source, symbol); } else { if (symbol != null) { LOG.warn("Attribute symbol is unneeded for " + host.getText()); } if (InjectionUtil.isImage(source, mimeType)) { return new ImageValueWriter(source, mimeType); } else { problemsHolder.add(host, FlashUIDesignerBundle.message("unsupported.embed.asset.type", host.getText())); return InjectedASWriter.IGNORE; } } } private boolean isUnexpected(String actualType) { if (actualType.equals(expectedType) || expectedType == null || isExpectedObjectOrAnyType()) { return false; } else { problemsHolder.add(host, "Expected " + expectedType + ", but got " + host.getText()); unsupported = true; return true; } } private boolean isExpectedObjectOrAnyType() { return JSCommonTypeNames.OBJECT_CLASS_NAME.equals(expectedType) || JSCommonTypeNames.ANY_TYPE.equals(expectedType); } private void logUnsupported() { LOG.warn("unsupported injected AS: " + host.getText()); unsupported = true; } }