package com.intellij.flex.uiDesigner.mxml; import com.intellij.flex.uiDesigner.AssetCounter; import com.intellij.flex.uiDesigner.FlashUIDesignerBundle; import com.intellij.flex.uiDesigner.InvalidPropertyException; import com.intellij.flex.uiDesigner.ProblemsHolder; import com.intellij.flex.uiDesigner.io.Amf3Types; import com.intellij.flex.uiDesigner.io.ByteRange; import com.intellij.flex.uiDesigner.io.Marker; import com.intellij.flex.uiDesigner.io.PrimitiveAmfOutputStream; import com.intellij.javascript.flex.FlexPredefinedTagNames; import com.intellij.javascript.flex.FlexStateElementNames; import com.intellij.javascript.flex.mxml.FlexCommonTypeNames; import com.intellij.javascript.flex.mxml.MxmlJSClass; import com.intellij.javascript.flex.mxml.schema.ClassBackedElementDescriptor; import com.intellij.javascript.flex.mxml.schema.MxmlBackedElementDescriptor; import com.intellij.javascript.flex.resolve.ActionScriptClassResolver; import com.intellij.lang.javascript.flex.AnnotationBackedDescriptor; import com.intellij.lang.javascript.psi.JSCommonTypeNames; import com.intellij.lang.javascript.psi.ecmal4.JSClass; import com.intellij.lang.javascript.psi.resolve.JSResolveUtil; import com.intellij.openapi.application.AccessToken; import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.RangeMarker; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Trinity; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.xml.*; import com.intellij.xml.XmlAttributeDescriptor; import com.intellij.xml.XmlElementDescriptor; import com.intellij.xml.impl.schema.AnyXmlAttributeDescriptor; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.PropertyKey; import java.io.IOException; import java.util.ArrayList; import java.util.List; import static com.intellij.flex.uiDesigner.mxml.PropertyProcessor.PropertyKind; public class MxmlWriter { static final Logger LOG = Logger.getInstance(MxmlWriter.class); private final PrimitiveAmfOutputStream out; private final BaseWriter writer; private StateWriter stateWriter; private final InjectedASWriter injectedASWriter; private final PropertyProcessor propertyProcessor; private final NullContext tagAttributeProcessContext; private boolean hasStates; final ProblemsHolder problemsHolder; final ValueProviderFactory valueProviderFactory = new ValueProviderFactory(); private final List<RangeMarker> rangeMarkers = new ArrayList<>(); final ProjectComponentReferenceCounter projectComponentReferenceCounter = new ProjectComponentReferenceCounter(); private Document document; public MxmlWriter(PrimitiveAmfOutputStream out, ProblemsHolder problemsHolder, AssetCounter assetCounter) { this.out = out; this.problemsHolder = problemsHolder; writer = new BaseWriter(out, assetCounter); tagAttributeProcessContext = writer.nullContext; injectedASWriter = new InjectedASWriter(writer, problemsHolder); propertyProcessor = new PropertyProcessor(injectedASWriter, writer, this); } @Nullable public Pair<ProjectComponentReferenceCounter, List<RangeMarker>> write(XmlFile psiFile) throws IOException { document = MxmlUtil.getDocumentAndWaitIfNotCommitted(psiFile); final AccessToken token = ReadAction.start(); try { VirtualFile virtualFile = psiFile.getVirtualFile(); LOG.assertTrue(virtualFile != null); problemsHolder.setCurrentFile(virtualFile); XmlTag tag = psiFile.getRootTag(); XmlElementDescriptor untypedDescriptor = tag == null ? null : tag.getDescriptor(); final ClassBackedElementDescriptor descriptor; if (untypedDescriptor instanceof ClassBackedElementDescriptor) { descriptor = (ClassBackedElementDescriptor)untypedDescriptor; } else { return null; } final Trinity<Integer, String, Condition<AnnotationBackedDescriptor>> effectiveClassInfo; try { PsiElement declaration = descriptor.getDeclaration(); if (declaration == null) { return null; } effectiveClassInfo = MxmlUtil.computeEffectiveClass(tag, declaration, projectComponentReferenceCounter, true); } catch (InvalidPropertyException e) { problemsHolder.add(e); return null; } if (effectiveClassInfo.first == -1) { out.write(Amf3Types.OBJECT); writer.mxmlObjectHeader(effectiveClassInfo.second == null ? descriptor.getQualifiedName() : effectiveClassInfo.second); } else { writer.documentReference(effectiveClassInfo.first); out.allocateClearShort(); } processElements(tag, null, false, -1, out.size() - 2, true, effectiveClassInfo.third); writer.endObject(); if (stateWriter != null) { stateWriter.write(); hasStates = false; } else { out.write(0); } injectedASWriter.write(); writer.writeMessageHeader(projectComponentReferenceCounter); return Pair.create(projectComponentReferenceCounter, rangeMarkers); } finally { token.finish(); problemsHolder.setCurrentFile(null); writer.resetAfterMessage(); } } @SuppressWarnings("StatementWithEmptyBody") private boolean processElements(final XmlTag tag, @Nullable final Context parentContext, final boolean allowIncludeInExcludeFrom, final int dataPosition, final int referencePosition, final boolean writeLocation, @Nullable final Condition<AnnotationBackedDescriptor> propertyFilter) { boolean staticChild = true; ByteRange dataRange = null; // if state specific property before includeIn, state override data range wil be added before object data range, so, // we keep current index and insert at the specified position final Marker dataRangeAfterAnchor = writer.getBlockOut().getLastMarker(); if (writeLocation) { out.writeUInt29(writer.P_FUD_RANGE_ID); out.writeUInt29(rangeMarkers.size()); rangeMarkers.add(document.createRangeMarker(tag.getTextOffset(), tag.getTextOffset() + tag.getTextLength())); } assert !tagAttributeProcessContext.isCssRulesetDefined(); Context context = tagAttributeProcessContext; if (parentContext != null && parentContext.getScope().staticObjectPointToScope) { tagAttributeProcessContext.setTempParentScope(parentContext.getScope()); } for (final XmlAttribute attribute : tag.getAttributes()) { if (attribute.getValueElement() == null) { // skip invalid - "<Button label/>" continue; } final XmlAttributeDescriptor attributeDescriptor = attribute.getDescriptor(); final AnnotationBackedDescriptor descriptor; if (attributeDescriptor instanceof AnnotationBackedDescriptor) { descriptor = (AnnotationBackedDescriptor)attributeDescriptor; // id and includeIn/excludeFrom only as attribute, not as tag if (descriptor.isPredefined()) { if (descriptor.hasIdType()) { processObjectWithExplicitId(attribute.getValue(), context); } else if (allowIncludeInExcludeFrom) { String name = descriptor.getName(); boolean excludeFrom = false; if (name.equals(FlexStateElementNames.INCLUDE_IN) || (excludeFrom = name.equals(FlexStateElementNames.EXCLUDE_FROM))) { if (context == tagAttributeProcessContext) { context = new DynamicObjectContext(tagAttributeProcessContext); } // must be before stateWriter.includeIn - start object data range before state data range dataRange = writer.getBlockOut().startRange(dataPosition, dataRangeAfterAnchor); ((DynamicObjectContext)context).setDataRange(dataRange); stateWriter.includeInOrExcludeFrom(attribute.getValueElement(), parentContext, (DynamicObjectContext)context, excludeFrom); staticChild = false; } else if (name.equals(FlexStateElementNames.ITEM_CREATION_POLICY)) { if (attribute.getValue().charAt(0) == 'i') { if (context == tagAttributeProcessContext) { context = new DynamicObjectContext(tagAttributeProcessContext); } ((DynamicObjectContext)context).setImmediateCreation(true); } } else if (name.equals(FlexStateElementNames.ITEM_DESTRUCTION_POLICY)) { if (attribute.getValue().charAt(0) == 'a') { stateWriter.applyItemAutoDestruction(context, parentContext); } } } } else if (propertyFilter != null && !propertyFilter.value(descriptor)) { // skip } else if (MxmlUtil.isIdLanguageAttribute(attribute, descriptor)) { String explicitId = attribute.getValue(); writer.idMxmlProperty(explicitId); processObjectWithExplicitId(explicitId, context); } else if (descriptor.getTypeName() == null) { //IDEA-73453 // skip LOG.warn("Skip " + descriptor.getName() + " in " + tag.getText() + " due to IDEA-73453"); } else if (hasStates && stateWriter.checkStateSpecificPropertyValue(this, propertyProcessor, valueProviderFactory.create(attribute), descriptor, context)) { // skip } else { writeAttributeBackedProperty(attribute, descriptor, context, context); } } else if (attributeDescriptor instanceof AnyXmlAttributeDescriptor) { writeAttributeBackedProperty(attribute, new AnyXmlAttributeDescriptorWrapper(attributeDescriptor), context, context); } else if (!attribute.isNamespaceDeclaration()) { LOG.warn("unknown attribute (" + attribute.getText() + ") descriptor: " + (attributeDescriptor == null ? "null" : attributeDescriptor.toString()) + " of tag " + tag.getText()); } } if (!hasStates) { assert context == tagAttributeProcessContext; context = writer.createStaticContext(parentContext, referencePosition); if (tagAttributeProcessContext.isCssRulesetDefined()) { context.markCssRulesetDefined(); } } else if (context == tagAttributeProcessContext) { context = stateWriter.createContextForStaticBackSibling(allowIncludeInExcludeFrom, referencePosition, parentContext); stateWriter.finalizeStateSpecificAttributesForStaticContext((StaticObjectContext)context, parentContext, this); if (tagAttributeProcessContext.isCssRulesetDefined()) { context.markCssRulesetDefined(); } } tagAttributeProcessContext.reset(); processTagChildren(tag, context, parentContext, true, null); // initializeReference must be after process all elements - after sub tag also, due to <RadioButton id="visa" label="Visa" // width="150"><group>{cardtype} !!id (for binding target, RadioButton id="visa") allocation here!!</group></RadioButton> if (dataPosition != -1) { writer.endObject(); if (dataRange != null) { writer.getBlockOut().endRange(dataRange); } } return staticChild; } void writeSimpleAttributeBackedProperty(XmlAttribute attribute, AnnotationBackedDescriptor descriptor, @NotNull MxmlObjectReferenceProvider mxmlObjectReferenceProvider) { writeAttributeBackedProperty(attribute, descriptor, mxmlObjectReferenceProvider, null); } // parentContext nullable only if simple attribute (@see writeSimpleAttributeBackedProperty) private void writeAttributeBackedProperty(XmlAttribute attribute, AnnotationBackedDescriptor descriptor, @NotNull MxmlObjectReferenceProvider mxmlObjectReferenceProvider, @Nullable Context parentContext) { final int beforePosition = out.size(); final PropertyKind propertyKind = writeProperty(attribute, valueProviderFactory.create(attribute), descriptor, mxmlObjectReferenceProvider, parentContext); if (propertyKind != PropertyKind.IGNORE) { if (propertyKind.isComplex()) { writer.getBlockOut().setPosition(beforePosition); addProblem(attribute, "unknown.attribute.value.type", descriptor.getType()); } } } boolean processTagChildren(final XmlTag tag, @NotNull final Context context, @Nullable final Context parentContext, final boolean propertiesExpected, @Nullable PropertyKind propertyKind) { int lengthPosition = propertyKind != null && propertyKind.isList() ? out.allocateShort() : 0; int explicitContentOccurred = -1; int validAndStaticChildrenCount = 0; final XmlTagChild[] children = tag.getValue().getChildren(); // if we process property tag value - if we cannot set value due to invalid content, so, we don't write property, // otherwise if there is no content, we write explicit null boolean invalidValue = false; for (XmlTagChild child : children) { if (child instanceof XmlTag) { XmlTag childTag = (XmlTag)child; XmlElementDescriptor descriptor = childTag.getDescriptor(); if (descriptor == null) { LOG.warn("Descriptor is null, skip " + child); invalidValue = true; continue; } assert descriptor != null; if (descriptor instanceof ClassBackedElementDescriptor) { final ClassBackedElementDescriptor classBackedDescriptor = (ClassBackedElementDescriptor)descriptor; if (classBackedDescriptor.isPredefined()) { if (MxmlUtil.isObjectLanguageTag(tag)) { // IDEA-73482 processPropertyTag(childTag, new AnyXmlAttributeDescriptorWrapper(descriptor), context); } else if (descriptor.getQualifiedName().equals(FlexPredefinedTagNames.DECLARATIONS)) { injectedASWriter.readDeclarations(this, childTag); } continue; } else if (MxmlUtil.isAbstract(classBackedDescriptor)) { addProblem(child, "abstract.class", classBackedDescriptor.getQualifiedName()); continue; } if (explicitContentOccurred == 1) { LOG.warn("Default content already processed, skip " + child); continue; } if (propertiesExpected && explicitContentOccurred == -1) { explicitContentOccurred = 0; final PropertyKind defaultPropertyKind = processDefaultProperty(tag, valueProviderFactory.create(childTag), classBackedDescriptor, children.length, context); if (defaultPropertyKind == null) { continue; } else if (defaultPropertyKind.isList()) { lengthPosition = out.allocateShort(); propertyKind = defaultPropertyKind; } else if (defaultPropertyKind == PropertyKind.PRIMITIVE) { validAndStaticChildrenCount++; continue; } } if (processClassBackedSubTag(childTag, classBackedDescriptor, context, propertyKind != null && propertyKind.isList())) { validAndStaticChildrenCount++; } } else if (propertiesExpected && descriptor instanceof AnnotationBackedDescriptor) { AnnotationBackedDescriptor annotationBackedDescriptor = (AnnotationBackedDescriptor)descriptor; // explicit content after contiguous child elements serving as the default property value // skip invalid, contiguous child elements already processed and explicit content (i.e. AnnotationBackedDescriptor, property childTag) was occurred if (explicitContentOccurred == 0) { explicitContentOccurred = 1; if (propertyKind != null && propertyKind.isList()) { endList(validAndStaticChildrenCount, lengthPosition); } } if (childTag.getNamespace().equals(MxmlJSClass.MXML_URI4) && childTag.getLocalName().equals(FlexStateElementNames.STATES)) { if (childTag.getSubTags().length != 0) { hasStates = true; assert parentContext == null; if (stateWriter == null) { stateWriter = new StateWriter(writer); } stateWriter.readDeclaration(childTag); } } else { processPropertyTag(childTag, annotationBackedDescriptor, context); } } } else if (child instanceof XmlText && !MxmlUtil.containsOnlyWhitespace(child)) { if (explicitContentOccurred == 1) { LOG.warn("Default content already processed, skip '" + child.getText().trim() + "'"); continue; } if (context.getChildrenType() != null && !context.getChildrenType().equals(JSCommonTypeNames.STRING_CLASS_NAME)) { LOG.warn("Illegal child type, skip '" + child.getText().trim() + "'"); continue; } if (propertiesExpected && explicitContentOccurred == -1) { explicitContentOccurred = 0; final XmlElementValueProvider valueProvider = valueProviderFactory.create((XmlText)child); final PropertyKind defaultPropertyKind = processDefaultProperty(tag, valueProvider, null, children.length, context); if (defaultPropertyKind == PropertyKind.IGNORE) { explicitContentOccurred = -1; continue; } else if (defaultPropertyKind == null) { continue; } else if (defaultPropertyKind.isList()) { lengthPosition = out.allocateShort(); propertyKind = defaultPropertyKind; } else if (defaultPropertyKind == PropertyKind.PRIMITIVE) { validAndStaticChildrenCount++; continue; } else { final ValueWriter valueWriter; try { valueWriter = propertyProcessor.processXmlTextAsDefaultPropertyWithComplexType(valueProvider, tag, context); } catch (InvalidPropertyException e) { // we don't need any out rollback - nothing is written yet problemsHolder.add(e); continue; } if (valueWriter == null) { throw new IllegalArgumentException("unexpected default property kind " + defaultPropertyKind); } else if (valueWriter == InjectedASWriter.IGNORE) { continue; } } } else if (propertyKind == PropertyKind.VECTOR) { LOG.warn("skip " + child + " due to IDEA-73478"); // IDEA-73478, XmlText allowed only for fx:Array, but not for fx:Vector (even with type String) break; } if (propertyKind != null && propertyKind == PropertyKind.COMPLEX) { invalidValue = true; LOG.warn("Text is not expected" + child); } else { writer.string(((XmlText)child).getValue()); validAndStaticChildrenCount++; } } } if (propertyKind != null && propertyKind.isList()) { endList(validAndStaticChildrenCount, lengthPosition); } else if (!propertiesExpected && validAndStaticChildrenCount == 0) { if (invalidValue) { return false; } // PropertyAsTagWithCommentedValueAsTag, replace Amf3Types.OBJECT to Amf3Types.NULL out.putByte(Amf3Types.NULL, out.size() - 1); } return true; } private void processPropertyTag(XmlTag tag, AnnotationBackedDescriptor annotationBackedDescriptor, @NotNull Context parentContext) { if (hasStates && stateWriter.checkStateSpecificPropertyValue(this, propertyProcessor, valueProviderFactory.create(tag), annotationBackedDescriptor, parentContext)) { return; } final int beforePosition = writer.getBlockOut().size(); final PropertyKind propertyKind = writeProperty(tag, valueProviderFactory.create(tag), annotationBackedDescriptor, parentContext, parentContext); if (propertyKind.isComplex()) { if (!processPropertyTagValue(annotationBackedDescriptor, tag, parentContext, propertyKind)) { writer.getBlockOut().setPosition(beforePosition); } } } private void endList(int validChildrenCount, int lengthPosition) { assert validChildrenCount < 65535; out.putShort(validChildrenCount, lengthPosition); } // process tag value, opposite to processTagChildren expects only ClassBackedSubTag or XmlText (attributes already processed or isn't expected) boolean processPropertyTagValue(@Nullable AnnotationBackedDescriptor descriptor, @NotNull XmlTag tag, @NotNull Context parentContext, @NotNull PropertyKind propertyKind) { PropertyKind listKind = propertyKind.isList() ? propertyKind : null; if (listKind != null && descriptor != null) { parentContext.processingPropertyName = descriptor.getName(); } boolean result = processTagChildren(tag, parentContext, null, false, propertyKind); parentContext.processingPropertyName = null; return result; } private boolean processClassBackedSubTag(final XmlTag tag, final ClassBackedElementDescriptor descriptor, @Nullable final Context parentContext, final boolean isListItem) { final boolean allowIncludeInExcludeFrom = hasStates && isListItem && parentContext != null; final Trinity<Integer,String,Condition<AnnotationBackedDescriptor>> effectiveClassInfo; final String effectiveClassName; try { if (propertyProcessor.writeTagIfFxOrFxg(tag, descriptor, parentContext, allowIncludeInExcludeFrom, out)) { return true; } effectiveClassInfo = MxmlUtil.computeEffectiveClass(tag, descriptor.getDeclaration(), projectComponentReferenceCounter, true); if ("mx.core.UIComponent".equals(effectiveClassInfo.second)) { effectiveClassName = MxmlUtil.UNKNOWN_COMPONENT_CLASS_NAME; } else { effectiveClassName = effectiveClassInfo.second; } } catch (InvalidPropertyException e) { problemsHolder.add(e); return false; } final int childDataPosition = out.size(); if (effectiveClassInfo.first == -1) { if (isListItem) { out.write(Amf3Types.OBJECT); } writer.classOrPropertyName(effectiveClassName == null ? descriptor.getQualifiedName() : effectiveClassName); } else { if (!isListItem) { // replace Amf3Types.OBJECT to AmfExtendedTypes.DOCUMENT_REFERENCE writer.getBlockOut().setPosition(writer.getBlockOut().size() - 1); } writer.documentReference(effectiveClassInfo.first); } return processElements(tag, parentContext, allowIncludeInExcludeFrom, childDataPosition, out.allocateClearShort(), JSResolveUtil.isAssignableType(FlexCommonTypeNames.IVISUAL_ELEMENT, descriptor.getQualifiedName(), descriptor.getDeclaration()) || JSResolveUtil.isAssignableType(FlexCommonTypeNames.FLASH_DISPLAY_OBJECT, descriptor.getQualifiedName(), descriptor.getDeclaration()), effectiveClassInfo.third); } boolean processMxmlVector(XmlTag tag, @Nullable Context parentContext, boolean allowIncludeInExcludeFrom) { final XmlAttribute typeAttribute = tag.getAttribute("type"); final String type; if (typeAttribute == null || StringUtil.isEmpty((type = typeAttribute.getDisplayValue()))) { LOG.warn("Skip " + tag + ", attribute type must be specified"); return false; } final XmlAttribute fixedAttribute = tag.getAttribute("fixed"); out.write(AmfExtendedTypes.MXML_VECTOR); writer.classOrPropertyName(type); String displayValue = fixedAttribute == null ? null : StringUtil.nullize(fixedAttribute.getDisplayValue()); out.write(displayValue != null && displayValue.charAt(0) == 't'); processTagChildren(tag, processIdAttributeOfFxTag(tag, parentContext, allowIncludeInExcludeFrom), parentContext, false, PropertyKind.VECTOR); return true; } StaticObjectContext processIdAttributeOfFxTag(XmlTag tag, @Nullable Context parentContext, boolean allowIncludeInExcludeFrom) { final StaticObjectContext context; final int referencePosition = out.allocateClearShort(); if (hasStates) { context = stateWriter.createContextForStaticBackSibling(allowIncludeInExcludeFrom, referencePosition, parentContext); } else { context = writer.createStaticContext(parentContext, referencePosition); } XmlAttribute idAttribute = tag.getAttribute("id"); if (idAttribute != null) { String id = idAttribute.getDisplayValue(); if (StringUtil.isEmpty(id)) { LOG.warn("Skip id attribute of " + tag + ", id is empty"); } else { // IDEA-73516 //noinspection ConstantConditions injectedASWriter.putMxmlObjectReference(id, context.getMxmlObjectReference()); } } return context; } void processDeclarations(XmlTag parent) { final int lengthPosition = out.allocateShort(); int validChildrenCount = 0; for (XmlTag tag : parent.getSubTags()) { ClassBackedElementDescriptor descriptor = (ClassBackedElementDescriptor)tag.getDescriptor(); assert descriptor != null; if (descriptor.isPredefined()) { // todo IDEA-72123 if (descriptor.getName().equals(FlexPredefinedTagNames.MODEL)) { if (propertyProcessor.processFxModel(tag)) { validChildrenCount++; } } continue; } else if (MxmlUtil.isComponentLanguageTag(tag)) { if (propertyProcessor.processFxComponent(tag, false)) { validChildrenCount++; } continue; } if (processClassBackedSubTag(tag, descriptor, null, true)) { validChildrenCount++; } } out.putShort(validChildrenCount, lengthPosition); } private static boolean isHaloNavigator(String className, JSClass jsClass) { return className.equals(FlexCommonTypeNames.ACCORDION) || className.equals(FlexCommonTypeNames.VIEW_STACK) || ActionScriptClassResolver.isParentClass(jsClass, FlexCommonTypeNames.ACCORDION) || ActionScriptClassResolver.isParentClass(jsClass, FlexCommonTypeNames.VIEW_STACK); } private void addProblem(XmlElement xmlElement, @PropertyKey(resourceBundle = FlashUIDesignerBundle.BUNDLE) String key, Object... params) { problemsHolder.add(xmlElement, FlashUIDesignerBundle.message(key, params)); } // descriptor will be null if child is XmlText @Nullable private PropertyKind processDefaultProperty(XmlTag parentTag, XmlElementValueProvider valueProvider, @Nullable ClassBackedElementDescriptor descriptor, int childrenLength, @NotNull Context context) { final ClassBackedElementDescriptor parentDescriptor = (ClassBackedElementDescriptor)parentTag.getDescriptor(); assert parentDescriptor != null; final AnnotationBackedDescriptor defaultDescriptor = parentDescriptor.getDefaultPropertyDescriptor(); final boolean isXmlText = descriptor == null; if (defaultDescriptor == null) { final String className = parentDescriptor.getQualifiedName(); final JSClass jsClass; if (parentDescriptor instanceof MxmlBackedElementDescriptor) { jsClass = (JSClass)ActionScriptClassResolver.findClassByQNameStatic(className, parentTag); } else { jsClass = (JSClass)parentDescriptor.getDeclaration(); } final boolean isDirectContainerImpl = className.equals(FlexCommonTypeNames.ICONTAINER); if (isDirectContainerImpl || ActionScriptClassResolver.isParentClass(jsClass, FlexCommonTypeNames.ICONTAINER)) { if (isXmlText) { addProblem(parentTag, "initializer.cannot.be.represented.in.text", parentTag.getLocalName()); return null; } if (!isDirectContainerImpl && isHaloNavigator(className, jsClass) && !ActionScriptClassResolver.isParentClass((JSClass)descriptor.getDeclaration(), FlexCommonTypeNames.INAVIGATOR_CONTENT)) { addProblem(parentTag, "children.must.be", parentTag.getLocalName(), FlexCommonTypeNames.INAVIGATOR_CONTENT); return null; } writer.classOrPropertyName("0"); out.write(AmfExtendedTypes.MX_CONTAINER_CHILDREN); return PropertyKind.ARRAY; } else { // http://youtrack.jetbrains.net/issue/IDEA-66565 addProblem(parentTag, "default.property.not.found", parentTag.getLocalName()); } } else { // xmlText as default property with injection, see BindingToDeferredInstanceFromBytesBase if (isXmlText && writeXmlTextAsDefaultPropertyInjectedValue(parentTag, valueProvider, defaultDescriptor, context)) { return null; } if (defaultDescriptor.getType().equals(JSCommonTypeNames.ARRAY_CLASS_NAME) && defaultDescriptor.getArrayType() != null) { final String elementType = defaultDescriptor.getArrayType(); context.setChildrenType(elementType); final boolean isString = elementType.equals(JSCommonTypeNames.STRING_CLASS_NAME); if (isString) { if (descriptor != null && !descriptor.getQualifiedName().equals(JSCommonTypeNames.STRING_CLASS_NAME)) { addProblem(parentTag, "children.must.be", parentTag.getLocalName(), elementType); return null; } } else { if (isXmlText) { addProblem(parentTag, "initializer.cannot.be.represented.in.text", parentTag.getLocalName()); return PropertyKind.IGNORE; } } } writer.classOrPropertyName(defaultDescriptor.getName()); if (defaultDescriptor.isDeferredInstance()) { writer.newInstance("com.intellij.flex.uiDesigner.flex.DeferredInstanceFromArray", 1, false).typeMarker(Amf3Types.ARRAY); return PropertyKind.ARRAY; } else { final String type = defaultDescriptor.getType(); if (type.equals(JSCommonTypeNames.STRING_CLASS_NAME) || (isXmlText && childrenLength == 1 && (type.equals(JSCommonTypeNames.OBJECT_CLASS_NAME) || type.equals(JSCommonTypeNames.ANY_TYPE)))) { writeSubstitutedString(valueProvider.getSubstituted()); } else if (defaultDescriptor.contentIsArrayable()) { out.write(type.equals(JSCommonTypeNames.ARRAY_CLASS_NAME) ? Amf3Types.ARRAY : AmfExtendedTypes.ARRAY_IF_LENGTH_GREATER_THAN_1); return PropertyKind.ARRAY; } else if (defaultDescriptor.getArrayType() != null /* Vector */) { if (isXmlText) { LOG.warn("skip " + valueProvider.getElement() + " due to IDEA-73478"); return null; } writer.vectorHeader(defaultDescriptor.getArrayType()); return PropertyKind.VECTOR; } else if (type.equals(JSCommonTypeNames.NUMBER_CLASS_NAME)) { out.writeAmfDouble(valueProvider.getTrimmed()); } else if (type.equals(JSCommonTypeNames.BOOLEAN_CLASS_NAME)) { out.writeAmfBoolean(valueProvider.getTrimmed()); } else { out.write(Amf3Types.OBJECT); return PropertyKind.COMPLEX; } } } return PropertyKind.PRIMITIVE; } private void writeSubstitutedString(CharSequence value) { if (value == XmlElementValueProvider.EMPTY) { writer.stringReference(XmlElementValueProvider.EMPTY); } else { writer.string(value); } } private void defineInlineCssRuleset(@NotNull PsiElement element) { int textOffset = element.getTextOffset(); out.writeUInt29(document.getLineNumber(textOffset) + 1); out.writeUInt29(textOffset); } private void processObjectWithExplicitId(String explicitId, @NotNull Context context) { injectedASWriter.putMxmlObjectReference(explicitId, context.getMxmlObjectReference()); } private PropertyKind writeProperty(XmlElement element, XmlElementValueProvider valueProvider, AnnotationBackedDescriptor descriptor, @NotNull MxmlObjectReferenceProvider objectReferenceProvider, @Nullable Context parentContext) { final int beforePosition = writer.getBlockOut().size(); try { ValueWriter valueWriter = propertyProcessor.process(element, valueProvider, descriptor, objectReferenceProvider); if (valueWriter == null) { return PropertyKind.IGNORE; } if (parentContext != null) { writePropertyHeader(propertyProcessor.getName(), element, parentContext, propertyProcessor.isStyle()); } else { writer.property(propertyProcessor.getName()); } // parentContext required only for process tag children, for attribute it can be null return valueWriter.write(descriptor, valueProvider, out, writer, propertyProcessor.isStyle(), parentContext); } catch (RuntimeException e) { problemsHolder.add(element, e, descriptor.getName()); } catch (Throwable e) { problemsHolder.add(e); } writer.getBlockOut().setPosition(beforePosition); return PropertyKind.IGNORE; } private void writePropertyHeader(String name, XmlElement element, @NotNull Context context, boolean isStyle) { writer.classOrPropertyName(name); if (isStyle) { out.write(AmfExtendedTypes.STYLE); if (!context.isCssRulesetDefined()) { defineInlineCssRuleset(element); context.markCssRulesetDefined(); } } } private boolean writeXmlTextAsDefaultPropertyInjectedValue(XmlElement element, XmlElementValueProvider valueProvider, AnnotationBackedDescriptor descriptor, @NotNull Context context) { final int beforePosition = writer.getBlockOut().size(); try { ValueWriter valueWriter = propertyProcessor.processInjected(valueProvider, descriptor, descriptor.isStyle(), context); if (valueWriter == null) { return false; } if (valueWriter != InjectedASWriter.IGNORE) { writePropertyHeader(descriptor.getName(), element, context, descriptor.isStyle()); valueWriter.write(descriptor, valueProvider, out, writer, propertyProcessor.isStyle(), context); } return true; } catch (RuntimeException e) { problemsHolder.add(element, e, descriptor.getName()); } catch (Throwable e) { problemsHolder.add(e); } writer.getBlockOut().setPosition(beforePosition); // true, ignore return true; } void setDeferredReferenceForObjectWithExplicitIdOrBinding(StaticInstanceReferenceInDeferredParentInstance staticReferenceInDeferredParentInstance, int referenceInstance) { assert tagAttributeProcessContext.mxmlObjectReference.id == referenceInstance; tagAttributeProcessContext.mxmlObjectReference.staticReferenceInDeferredParentInstance = staticReferenceInDeferredParentInstance; tagAttributeProcessContext.mxmlObjectReference = null; } }