package com.intellij.flex.uiDesigner.mxml;
import com.google.common.base.CharMatcher;
import com.intellij.flex.uiDesigner.EmbedSwfManager;
import com.intellij.flex.uiDesigner.InjectionUtil;
import com.intellij.flex.uiDesigner.InvalidPropertyException;
import com.intellij.flex.uiDesigner.io.Amf3Types;
import com.intellij.flex.uiDesigner.io.PrimitiveAmfOutputStream;
import com.intellij.javascript.flex.FlexAnnotationNames;
import com.intellij.javascript.flex.mxml.FlexCommonTypeNames;
import com.intellij.javascript.flex.mxml.schema.ClassBackedElementDescriptor;
import com.intellij.javascript.flex.mxml.schema.CodeContext;
import com.intellij.javascript.flex.resolve.ActionScriptClassResolver;
import com.intellij.lang.javascript.JavaScriptSupportLoader;
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.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.util.Condition;
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.PsiFile;
import com.intellij.psi.meta.PsiMetaData;
import com.intellij.psi.xml.*;
import com.intellij.util.StringBuilderSpinAllocator;
import com.intellij.util.containers.ObjectIntHashMap;
import com.intellij.xml.XmlElementDescriptor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.intellij.flex.uiDesigner.mxml.MxmlWriter.LOG;
import static com.intellij.flex.uiDesigner.mxml.PropertyProcessor.PropertyKind.*;
class PropertyProcessor implements ValueWriter {
enum PropertyKind {
ARRAY, VECTOR, COMPLEX, COMPLEX_STYLE, PRIMITIVE, PRIMITIVE_STYLE, IGNORE;
boolean isComplex() {
return ordinal() < PRIMITIVE.ordinal();
}
boolean isList() {
return ordinal() < COMPLEX.ordinal();
}
}
private final InjectedASWriter injectedASWriter;
private final MxmlWriter mxmlWriter;
private final BaseWriter writer;
private String name;
private boolean isEffect;
private boolean isStyle;
private final ObjectIntHashMap<String> classFactoryMap = new ObjectIntHashMap<>();
PropertyProcessor(InjectedASWriter injectedASWriter, BaseWriter writer, MxmlWriter mxmlWriter) {
this.injectedASWriter = injectedASWriter;
this.writer = writer;
this.mxmlWriter = mxmlWriter;
}
public String getName() {
return name;
}
public boolean isStyle() {
return isStyle;
}
public boolean isEffect() {
return isEffect;
}
private ValueWriter processPercentable(XmlElementValueProvider valueProvider, AnnotationBackedDescriptor descriptor)
throws InvalidPropertyException {
String value = valueProvider.getTrimmed();
final boolean hasPercent;
if (value.isEmpty() || ((hasPercent = value.endsWith("%")) && value.length() == 1)) {
throw new InvalidPropertyException(valueProvider.getElement(), "invalid.numeric.value");
}
if (hasPercent) {
name = descriptor.getPercentProxy();
value = value.substring(0, value.length() - 1);
}
else {
name = descriptor.getName();
}
return new PercentableValueWriter(value);
}
@Nullable
public ValueWriter process(XmlElement element, XmlElementValueProvider valueProvider, AnnotationBackedDescriptor descriptor,
@NotNull MxmlObjectReferenceProvider objectReferenceProvider) throws InvalidPropertyException {
if (descriptor.isPredefined()) {
LOG.error("unknown language element " + descriptor.getName());
return null;
}
name = descriptor.getName();
isStyle = descriptor.isStyle();
isEffect = false;
final @Nullable String type = descriptor.getType();
final String typeName = descriptor.getTypeName();
if (type == null) {
if (typeName.equals(FlexAnnotationNames.EFFECT)) {
isStyle = true;
isEffect = true;
}
else {
// skip binding and event AS-423
if (!typeName.equals(FlexAnnotationNames.BINDABLE) && !typeName.equals(FlexAnnotationNames.EVENT)) {
LOG.error("unsupported element: " + element.getText());
}
return null;
}
}
else if (type.equals(JSCommonTypeNames.FUNCTION_CLASS_NAME)) {
if (name.equals("itemRendererFunction")) {
// AS-135
if (MxmlUtil.isPropertyOfSparkDataGroup(descriptor)) {
name = "itemRenderer";
return new ValueWriter() {
@Override
public PropertyKind write(AnnotationBackedDescriptor descriptor,
XmlElementValueProvider valueProvider,
PrimitiveAmfOutputStream out,
BaseWriter writer,
boolean isStyle,
Context parentContext) {
writeNonProjectClassFactory(MxmlUtil.UNKNOWN_ITEM_RENDERER_CLASS_NAME);
return PRIMITIVE;
}
};
}
}
// skip functions, IDEA-74041
}
else if (typeName.equals(FlexAnnotationNames.EVENT)) {
// skip event handlers
return null;
}
ValueWriter valueWriter = processInjected(valueProvider, descriptor, isStyle, objectReferenceProvider);
if (valueWriter != null) {
return valueWriter == InjectedASWriter.IGNORE ? null : valueWriter;
}
else if (descriptor.isAllowsPercentage()) {
return processPercentable(valueProvider, descriptor);
}
else if (isSkinClass(descriptor)) {
valueWriter = getSkinProjectClassValueWriter(getSkinProjectClassDocumentFactoryId(valueProvider));
if (valueWriter != null) {
return valueWriter;
}
}
return this;
}
public ValueWriter processXmlTextAsDefaultPropertyWithComplexType(XmlElementValueProvider valueProvider, XmlTag parent, Context context)
throws InvalidPropertyException {
ClassBackedElementDescriptor classBackedElementDescriptor = (ClassBackedElementDescriptor)parent.getDescriptor();
LOG.assertTrue(classBackedElementDescriptor != null);
AnnotationBackedDescriptor defaultPropertyDescriptor = classBackedElementDescriptor.getDefaultPropertyDescriptor();
LOG.assertTrue(defaultPropertyDescriptor != null);
return processInjected(valueProvider, defaultPropertyDescriptor, defaultPropertyDescriptor.isStyle(), context);
}
public ValueWriter processInjected(XmlElementValueProvider valueProvider, AnnotationBackedDescriptor descriptor, boolean isStyle, @NotNull MxmlObjectReferenceProvider mxmlObjectReferenceProvider)
throws InvalidPropertyException {
ValueWriter valueWriter = injectedASWriter.processProperty(valueProvider, descriptor.getName(), descriptor.getType(), isStyle, mxmlObjectReferenceProvider);
if (valueWriter instanceof ClassValueWriter && isSkinClass(descriptor)) {
SkinProjectClassValueWriter skinProjectClassValueWriter = getSkinProjectClassValueWriter(
getProjectComponentFactoryId(((ClassValueWriter)valueWriter).getJsClass()));
if (skinProjectClassValueWriter != null) {
return skinProjectClassValueWriter;
}
}
return valueWriter;
}
private boolean isSkinClass(AnnotationBackedDescriptor descriptor) {
return isStyle && name.equals("skinClass") && AsCommonTypeNames.CLASS.equals(descriptor.getType());
}
@Nullable
private SkinProjectClassValueWriter getSkinProjectClassValueWriter(int skinProjectClassDocumentFactoryId) {
if (skinProjectClassDocumentFactoryId != -1) {
name = "skinFactory";
return new SkinProjectClassValueWriter(skinProjectClassDocumentFactoryId);
}
else {
return null;
}
}
private int getSkinProjectClassDocumentFactoryId(XmlElementValueProvider valueProvider) throws InvalidPropertyException {
JSClass jsClass = valueProvider.getJsClass();
return jsClass != null ? getProjectComponentFactoryId(jsClass) : -1;
}
private int getProjectComponentFactoryId(JSClass jsClass) throws InvalidPropertyException {
return InjectionUtil.getProjectComponentFactoryId(jsClass, mxmlWriter.projectComponentReferenceCounter);
}
boolean processFxModel(XmlTag tag) {
final MxmlObjectReference objectReference = processFxDeclarationId(tag, null, false);
if (objectReference == null) {
return false;
}
writer.referableHeader(objectReference.id);
final XmlTag[] subTags = tag.getSubTags();
if (subTags.length == 1) {
final ModelObjectReferenceProvider modelObjectReference = new ModelObjectReferenceProvider(writer);
modelObjectReference.reference = objectReference;
processFxModelTagChildren(subTags[0], modelObjectReference);
}
else {
// as object without any properties
writer.objectHeader("mx.utils.ObjectProxy");
writer.endObject();
if (subTags.length > 1) {
LOG.warn("Skip model, only one root tag is allowed: " + tag.getText());
}
}
return true;
}
@Nullable
private MxmlObjectReference processFxDeclarationId(XmlTag tag, @Nullable String classNameTrick, boolean allowNullableIdOrClassName) {
String id = getAttributeValue(tag, "id");
if (id == null) {
if (classNameTrick == null) {
if (!allowNullableIdOrClassName) {
LOG.warn("Skip fx:model/fx:component, id is not specified or empty: " + tag.getText());
}
return null;
}
}
// parentContext for fx:Model/fx:Component always null, because located inside fx:Declarations (i.e. parentContext always is top level)
// state specific is not allowed for fx:Model/fx:Component (flex compiler doesn't support it)
final MxmlObjectReference objectReference = new MxmlObjectReference(writer.allocateAbsoluteStaticObjectId());
if (id != null) {
injectedASWriter.putMxmlObjectReference(id, objectReference);
}
if (classNameTrick != null) {
injectedASWriter.putMxmlObjectReference('$' + classNameTrick, objectReference);
}
return objectReference;
}
static String getAttributeValue(XmlTag tag, String attributeName) {
final XmlAttribute attribute = tag.getAttribute(attributeName);
return attribute == null ? null : StringUtil.nullize(attribute.getDisplayValue());
}
boolean processFxComponent(XmlTag tag, boolean allowNullableIdOrClassName) {
String className = getAttributeValue(tag, "className");
if (className != null && writeFxComponentReferenceIfProcessed(className)) {
return true;
}
final MxmlObjectReference objectReference = processFxDeclarationId(tag, className, allowNullableIdOrClassName);
if (objectReference == null && !allowNullableIdOrClassName) {
return false;
}
final int objectReferenceId = objectReference == null ? writer.allocateAbsoluteStaticObjectId() : objectReference.id;
final int sizePosition = writer.componentFactory(objectReferenceId);
final XmlTag[] subTags = tag.getSubTags();
final int objectTableSize;
final PrimitiveAmfOutputStream out = writer.getOut();
if (subTags.length == 1) {
out.write(Amf3Types.OBJECT);
Context context = writer.createInnerComponentContext(objectReference, objectReferenceId);
mxmlWriter.processPropertyTagValue(null, tag, context, COMPLEX);
objectTableSize = context.getScope().referenceCounter;
}
else {
// as object without any properties
writer.objectHeader("Object");
writer.endObject();
if (subTags.length > 1) {
LOG.warn("Skip fx:component, only one root tag is allowed: " + tag.getText());
}
objectTableSize = 0;
}
out.putShort(out.size() - (sizePosition + 2 /* short size */), sizePosition);
out.putShort(objectTableSize, sizePosition + 2);
return true;
}
private static class ModelObjectReferenceProvider implements MxmlObjectReferenceProvider {
private MxmlObjectReference reference;
private final BaseWriter writer;
public ModelObjectReferenceProvider(BaseWriter writer) {
this.writer = writer;
}
@Override
public MxmlObjectReference getMxmlObjectReference() {
if (reference == null) {
reference = new MxmlObjectReference(writer.allocateAbsoluteStaticObjectId());
writer.property("2");
writer.getOut().writeUInt29(reference.id);
}
return reference;
}
}
// tagLocalName is null, if parent is map item (i.e. not as property value, but as array item)
private int writeFxModelTagIfContainsXmlText(XmlTag parent, @Nullable String tagLocalName,
ModelObjectReferenceProvider objectReferenceProvider) {
for (XmlTagChild child : parent.getValue().getChildren()) {
// ignore any subtags if XmlText presents, according to flex compiler behavior
if (child instanceof XmlText && !MxmlUtil.containsOnlyWhitespace(child)) {
final ValueWriter valueWriter;
try {
valueWriter = injectedASWriter.processProperty(child, tagLocalName, null, false, objectReferenceProvider);
}
catch (InvalidPropertyException e) {
// we don't need any out rollback - nothing is written yet
mxmlWriter.problemsHolder.add(e);
return -1;
}
if (valueWriter != InjectedASWriter.IGNORE) {
if (valueWriter != null) {
throw new IllegalStateException("What?");
}
else {
if (tagLocalName != null) {
writer.property(tagLocalName);
}
writeUntypedPrimitiveValue(writer.getOut(), ((XmlText)child).getValue());
}
}
// ignore any attributes
return 1;
}
else if (child instanceof XmlTag) {
return 0;
}
}
return 0;
}
private boolean processFxModelTagChildren(final XmlTag parent, ModelObjectReferenceProvider parentObjectReferenceProvider) {
writer.objectHeader("mx.utils.ObjectProxy");
final XmlTag[] parentSubTags = parent.getSubTags();
for (XmlTag tag : parentSubTags) {
final String tagLocalName = tag.getLocalName();
final XmlTag[] subTags = parent.findSubTags(tagLocalName, tag.getNamespace());
if (subTags.length > 1) {
writer.property(tagLocalName);
final int lengthPosition = writer.arrayHeader();
int length = 0;
ModelObjectReferenceProvider subObjectReferenceProvider = null;
for (XmlTag subTag : subTags) {
final int result = writeFxModelTagIfContainsXmlText(subTag, null, parentObjectReferenceProvider);
if (result == 1) {
length++;
}
else if (result == 0) {
if (subObjectReferenceProvider == null) {
subObjectReferenceProvider = new ModelObjectReferenceProvider(writer);
}
if (processFxModelTagChildren(subTag, subObjectReferenceProvider)) {
length++;
}
subObjectReferenceProvider.reference = null;
}
}
writer.getOut().putShort(length, lengthPosition);
}
else if (writeFxModelTagIfContainsXmlText(tag, tagLocalName, parentObjectReferenceProvider) == 0) {
writer.property(tagLocalName);
processFxModelTagChildren(tag, new ModelObjectReferenceProvider(writer));
}
}
for (final XmlAttribute attribute : parent.getAttributes()) {
mxmlWriter.writeSimpleAttributeBackedProperty(attribute, new AnyXmlAttributeDescriptorWrapper(attribute.getDescriptor()),
parentObjectReferenceProvider);
}
writer.endObject();
return true;
}
boolean writeTagIfFxOrFxg(XmlTag tag, XmlElementDescriptor descriptor, @Nullable Context parentContext, boolean allowIncludeInExcludeFrom, PrimitiveAmfOutputStream out) throws InvalidPropertyException {
String type = descriptor.getQualifiedName();
// AS-110
if (!JavaScriptSupportLoader.MXML_URI3.equals(tag.getNamespace())) {
return writeIfFxg(tag, descriptor, parentContext, allowIncludeInExcludeFrom, out);
}
else if (type.equals(JSCommonTypeNames.OBJECT_CLASS_NAME) || type.equals(AsCommonTypeNames.DATE)) {
return false;
}
if (JSCommonTypeNames.ARRAY_CLASS_NAME.equals(type)) {
// see valArr in EmbedSwfAndImageFromCss
out.write(AmfExtendedTypes.MXML_ARRAY);
mxmlWriter.processTagChildren(tag, mxmlWriter.processIdAttributeOfFxTag(tag, parentContext, allowIncludeInExcludeFrom), parentContext,
false, ARRAY);
return true;
}
else if (CodeContext.AS3_VEC_VECTOR_QUALIFIED_NAME.equals(type)) {
return mxmlWriter.processMxmlVector(tag, parentContext, allowIncludeInExcludeFrom);
}
final boolean isXml;
if (type.equals(JSCommonTypeNames.XML_LIST_CLASS_NAME)) {
out.write(AmfExtendedTypes.XML_LIST);
isXml = true;
}
else if (type.equals(JSCommonTypeNames.XML_CLASS_NAME)) {
out.write(AmfExtendedTypes.XML);
isXml = true;
}
else {
out.write(AmfExtendedTypes.REFERABLE);
isXml = false;
}
mxmlWriter.processIdAttributeOfFxTag(tag, parentContext, allowIncludeInExcludeFrom);
if (isXml) {
out.writeAmfUtf(tag.getValue().getText());
}
else {
boolean result = writeIfPrimitive(mxmlWriter.valueProviderFactory.create(tag), type, out, null, false, true);
LOG.assertTrue(result);
}
return true;
}
private boolean writeIfFxg(XmlTag tag, XmlElementDescriptor descriptor, Context parentContext, boolean allowIncludeInExcludeFrom, PrimitiveAmfOutputStream out) {
if (descriptor instanceof ClassBackedElementDescriptor) {
PsiElement declaration = descriptor.getDeclaration();
PsiFile psiFile = declaration.getContainingFile();
VirtualFile virtualFile = psiFile.getVirtualFile();
MxmlWriter.LOG.assertTrue(virtualFile != null);
if (JavaScriptSupportLoader.isFxgFile(virtualFile)) {
out.write(AmfExtendedTypes.REFERABLE);
mxmlWriter.processIdAttributeOfFxTag(tag, parentContext, allowIncludeInExcludeFrom);
out.write(AmfExtendedTypes.SWF);
out.writeUInt29(EmbedSwfManager.getInstance().add(virtualFile, EmbedSwfManager.FXG_MARKER, writer.getAssetCounter()));
return true;
}
}
return false;
}
boolean writeIfPrimitive(XmlElementValueProvider valueProvider, String type, PrimitiveAmfOutputStream out, @Nullable AnnotationBackedDescriptor descriptor,
boolean isStyle, boolean emptyNumericAs0) throws InvalidPropertyException {
if (writer.writeIfApplicable(valueProvider, type, out, descriptor, isStyle, emptyNumericAs0)) {
return true;
}
else if (type.equals(AsCommonTypeNames.CLASS)) {
processClass(valueProvider);
return true;
}
return false;
}
// see ClassProperty test
private void processClass(XmlElementValueProvider valueProvider) throws InvalidPropertyException {
JSClass jsClass = valueProvider.getJsClass();
// IDEA-73537, cannot use only valueProvider.getJsClass()
if (jsClass == null) {
String trimmed = valueProvider.getTrimmed();
XmlElement exceptionElement = valueProvider.getElement();
if (trimmed.isEmpty() && valueProvider.getElement() instanceof XmlTag) {
// case 1, fx:Class
final XmlTag propertyTag = (XmlTag)valueProvider.getElement();
final XmlTag[] propertyTagSubTags = propertyTag.getSubTags();
if (propertyTagSubTags.length == 1) {
final XmlTag contentTag = propertyTagSubTags[0];
exceptionElement = contentTag;
final XmlElementDescriptor contentTagDescriptor = contentTag.getDescriptor();
if (contentTagDescriptor instanceof ClassBackedElementDescriptor &&
AsCommonTypeNames.CLASS.equals(contentTagDescriptor.getQualifiedName())) {
trimmed = contentTag.getValue().getTrimmedText();
}
}
}
if (trimmed.isEmpty()) {
throw new InvalidPropertyException(exceptionElement, "invalid.class.value");
}
final Module module = ModuleUtilCore.findModuleForPsiElement(valueProvider.getElement());
if (module != null) {
jsClass = (JSClass)ActionScriptClassResolver
.findClassByQNameStatic(trimmed, module.getModuleWithDependenciesAndLibrariesScope(false));
}
if (jsClass == null) {
throw new InvalidPropertyException(exceptionElement, "unresolved.class", trimmed);
}
}
if (InjectionUtil.isProjectComponent(jsClass)) {
if (ActionScriptClassResolver.isParentClass(jsClass, "spark.components.View")) {
int projectComponentFactoryId = getProjectComponentFactoryId(jsClass);
assert projectComponentFactoryId != -1;
writer.projectClassReference(projectComponentFactoryId);
}
else {
throw new InvalidPropertyException(valueProvider.getElement(), "class.reference.support.only.skin.class.or.view", jsClass.getQualifiedName());
}
}
else {
writer.classReference(jsClass.getQualifiedName());
}
}
@SuppressWarnings("StatementWithEmptyBody")
@Override
public PropertyKind write(AnnotationBackedDescriptor descriptor, XmlElementValueProvider valueProvider, PrimitiveAmfOutputStream out,
BaseWriter writer, boolean isStyle, @Nullable Context parentContext) throws InvalidPropertyException {
final String type = descriptor.getType();
if (isStyle) {
int flags = 0;
if (isEffect()) {
flags |= StyleFlags.EFFECT;
out.write(flags);
out.write(Amf3Types.OBJECT);
return COMPLEX_STYLE;
}
else {
out.write(flags);
}
}
else if (isEffect()) {
out.write(Amf3Types.OBJECT);
return COMPLEX;
}
if (writeIfPrimitive(valueProvider, type, out, descriptor, isStyle, false)) {
}
else if (type.equals(JSCommonTypeNames.ARRAY_CLASS_NAME)) {
if (!descriptor.isRichTextContent() && valueProvider instanceof XmlAttributeValueProvider &&
isInlineArray(valueProvider.getTrimmed())) {
writeInlineArray(valueProvider);
}
else {
out.write(Amf3Types.ARRAY);
return ARRAY;
}
}
else if (descriptor.getArrayType() != null) {
// test for mxml vector
// Why we don't it for array?
//<mx:selectedIndices>
// <fx:Array>
// <fx:int>1</fx:int>
// <fx:int>2</fx:int>
// </fx:Array>y
// </mx:selectedIndices>
//
// will be compiled fine as [[1, 2], "y\n "]
//
// but vector
//<fx:Vector type="int" fixed="true">
// <fx:int>1</fx:int>
// <fx:int>2</fx:int>
// </fx:Vector>y
// will not be compiled
// [untitled] In initializer for 'selectedIndices', type __AS3__.vec.Vector.<int> is not assignable to target __AS3__.vec.Vector.<int> element type int.
// [untitled] In initializer for 'selectedIndices', type Object is not assignable to target __AS3__.vec.Vector.<int> element type int.
// so, we need check only for mxml vector
if (valueProvider.getElement() instanceof XmlTag) {
final XmlTag propertyTag = (XmlTag)valueProvider.getElement();
final XmlTag[] subTags = propertyTag.getSubTags();
if (subTags.length == 1) {
final XmlTag contentTag = subTags[0];
final XmlElementDescriptor contentTagDescriptor = contentTag.getDescriptor();
if (contentTagDescriptor instanceof ClassBackedElementDescriptor &&
CodeContext.AS3_VEC_VECTOR_QUALIFIED_NAME.equals(contentTagDescriptor.getQualifiedName())) {
if (!mxmlWriter.processMxmlVector(contentTag, parentContext, false)) {
throw new InvalidPropertyException(contentTag, "invalid.vector.value");
}
return isStyle ? PRIMITIVE_STYLE : PRIMITIVE;
}
}
}
writer.vectorHeader(descriptor.getArrayType());
return VECTOR;
}
else if (type.equals(JSCommonTypeNames.OBJECT_CLASS_NAME) || type.equals(JSCommonTypeNames.ANY_TYPE)) {
final PropertyKind propertyKind = writeUntypedPropertyValue(out, valueProvider, descriptor, isStyle, parentContext);
if (propertyKind != null) {
return propertyKind;
}
}
else if (type.equals(FlexCommonTypeNames.IFACTORY)) {
writeClassFactory(valueProvider);
}
else {
out.write(Amf3Types.OBJECT);
return isStyle ? COMPLEX_STYLE : COMPLEX;
}
return isStyle ? PRIMITIVE_STYLE : PRIMITIVE;
}
// IDEA-64721, IDEA-72814, see flex2.compiler.mxml.lang.TextParser
private static boolean isInlineArray(String text) {
return text.length() >= 2 && text.charAt(0) == '[' && text.charAt(text.length() - 1) == ']';
}
private void writeInlineArray(XmlElementValueProvider valueProvider) {
final PrimitiveAmfOutputStream out = writer.getOut();
out.write(Amf3Types.ARRAY);
final int lengthPosition = out.allocateShort();
int validChildrenCount = 0;
final StringBuilder builder = StringBuilderSpinAllocator.alloc();
final String value = valueProvider.getTrimmed();
try {
char quoteChar = '\'';
boolean inQuotes = false;
for (int index = 1, length = value.length(); index < length; index++) {
char c = value.charAt(index);
switch (c) {
case '[':
if (inQuotes) {
builder.append(c);
}
break;
case '"':
case '\'':
if (inQuotes) {
if (quoteChar == c) {
inQuotes = false;
}
else {
builder.append(c);
}
}
else {
inQuotes = true;
quoteChar = c;
}
break;
case ',':
case ']':
if (inQuotes) {
builder.append(c);
}
else {
int beginIndex = 0;
int endIndex = builder.length();
while (beginIndex < endIndex && builder.charAt(beginIndex) <= ' ') {
beginIndex++;
}
while (beginIndex < endIndex && builder.charAt(endIndex - 1) <= ' ') {
endIndex--;
}
if (endIndex == 0) {
writer.stringReference(XmlElementValueProvider.EMPTY);
}
else {
out.write(Amf3Types.STRING);
out.writeAmfUtf(builder, false, beginIndex, endIndex);
}
validChildrenCount++;
builder.setLength(0);
}
break;
default:
builder.append(c);
}
}
}
finally {
StringBuilderSpinAllocator.dispose(builder);
}
out.putShort(validChildrenCount, lengthPosition);
}
private PropertyKind writeUntypedPropertyValue(PrimitiveAmfOutputStream out, XmlElementValueProvider valueProvider,
AnnotationBackedDescriptor descriptor, boolean isStyle, @Nullable Context parentContext)
throws InvalidPropertyException {
if (descriptor.isRichTextContent()) {
writer.writeString(valueProvider, descriptor);
return null;
}
if (valueProvider instanceof XmlAttributeValueProvider && isInlineArray(valueProvider.getTrimmed())) {
writeInlineArray(valueProvider);
return null;
}
final CharSequence charSequence;
// IDEA-73099, IDEA-73960
if (valueProvider instanceof XmlTagValueProvider) {
final XmlTag tag = ((XmlTagValueProvider)valueProvider).getTag();
final XmlTagChild[] children = tag.getValue().getChildren();
int subTagsLength = 0;
XmlText xmlText = null;
for (XmlTagChild child : children) {
if (child instanceof XmlTag) {
subTagsLength++;
if (xmlText != null || subTagsLength > 1) {
break;
}
}
else if (child instanceof XmlText && !MxmlUtil.containsOnlyWhitespace(child)) {
assert xmlText == null;
xmlText = (XmlText)child;
}
}
if (subTagsLength == 0) {
if (xmlText == null) {
writer.stringReference(XmlElementValueProvider.EMPTY);
return null;
}
else {
charSequence = XmlTextValueProvider.getSubstituted(xmlText);
}
}
else if (subTagsLength == 1 && xmlText == null) {
final XmlTag childTag = tag.getSubTags()[0];
final XmlElementDescriptor childTagDescriptor = childTag.getDescriptor();
LOG.assertTrue(childTagDescriptor != null);
if (writeTagIfFxOrFxg(childTag, childTagDescriptor, parentContext, false, out)) {
return null;
}
else {
out.write(Amf3Types.OBJECT);
return isStyle ? COMPLEX_STYLE : COMPLEX;
}
}
else {
out.write(Amf3Types.ARRAY);
return ARRAY;
}
}
else {
charSequence = writer.writeIfEmpty(valueProvider);
}
if (charSequence != null) {
writeUntypedPrimitiveValue(out, charSequence);
}
return null;
}
private void writeUntypedPrimitiveValue(PrimitiveAmfOutputStream out, CharSequence charSequence) {
final String s = CharMatcher.WHITESPACE.trimFrom(charSequence);
if (s.equals("true")) {
out.write(Amf3Types.TRUE);
return;
}
if (s.equals("false")) {
out.write(Amf3Types.FALSE);
return;
}
//noinspection UnusedCatchParameter
try {
out.writeAmfInt(Integer.parseInt(s));
}
catch (NumberFormatException e) {
try {
out.writeAmfDouble(Double.parseDouble(s));
}
catch (NumberFormatException ignored) {
// write untrimmed string
writer.string(charSequence);
}
}
}
private void writeClassFactory(XmlElementValueProvider valueProvider) throws InvalidPropertyException {
if (valueProvider instanceof XmlTagValueProvider) {
XmlTag tag = ((XmlTagValueProvider)valueProvider).getTag();
XmlTag[] subTags = tag.getSubTags();
if (subTags.length > 0) {
processFxComponent(subTags[0], true);
return;
}
}
String className = valueProvider.getTrimmed();
if (writeFxComponentReferenceIfProcessed(className) || writeReferenceIfReferenced(className)) {
return;
}
final JSClass jsClass = valueProvider.getJsClass();
if (jsClass == null) {
throw new InvalidPropertyException(valueProvider.getElement(), "unresolved.class", valueProvider.getTrimmed());
}
final Trinity<Integer, String, Condition<AnnotationBackedDescriptor>> effectiveClassInfo;
final PsiElement parent = jsClass.getNavigationElement().getParent();
if (parent instanceof XmlTag && MxmlUtil.isComponentLanguageTag((XmlTag)parent)) {
// if referenced by inner class name, but inner fx component is not yet processed
if (parent.getContainingFile().equals(valueProvider.getElement().getContainingFile())) {
processFxComponent((XmlTag)parent, false);
return;
}
else {
effectiveClassInfo = new Trinity<>(-1, "mx.core.UIComponent", null);
}
}
else {
effectiveClassInfo =
MxmlUtil.computeEffectiveClass(valueProvider.getElement(), jsClass, mxmlWriter.projectComponentReferenceCounter, false);
}
if (effectiveClassInfo.first == -1) {
if (effectiveClassInfo.second != null) {
if (effectiveClassInfo.second.equals("mx.core.UIComponent")) {
PsiMetaData psiMetaData = valueProvider.getPsiMetaData();
if (psiMetaData != null &&
psiMetaData.getName().equals("itemRenderer") &&
MxmlUtil.isPropertyOfSparkDataGroup((AnnotationBackedDescriptor)psiMetaData)) {
className = MxmlUtil.UNKNOWN_ITEM_RENDERER_CLASS_NAME;
}
else {
className = MxmlUtil.UNKNOWN_COMPONENT_CLASS_NAME;
}
}
else {
className = effectiveClassInfo.second;
}
}
writeNonProjectUnreferencedClassFactory(className);
}
else {
writer.documentFactoryReference(effectiveClassInfo.first);
}
}
private boolean writeFxComponentReferenceIfProcessed(String className) {
MxmlObjectReference reference = injectedASWriter.getNullableValueReference('$' + className);
if (reference != null) {
writer.objectReference(reference.id);
return true;
}
return false;
}
private void writeNonProjectUnreferencedClassFactory(String className) {
int reference = writer.allocateAbsoluteStaticObjectId();
classFactoryMap.put(className, reference);
writer.referableHeader(reference).newInstance(FlexCommonTypeNames.CLASS_FACTORY, 1, false).classReference(className);
}
private void writeNonProjectClassFactory(String className) {
if (!writeReferenceIfReferenced(className)) {
writeNonProjectUnreferencedClassFactory(className);
}
}
private boolean writeReferenceIfReferenced(String className) {
int reference = classFactoryMap.get(className);
if (reference != -1) {
writer.objectReference(reference);
return true;
}
return false;
}
}