package com.intellij.flex.uiDesigner.mxml; import com.intellij.flex.uiDesigner.InvalidPropertyException; import com.intellij.flex.uiDesigner.io.BlockDataOutputStream; import com.intellij.flex.uiDesigner.io.ByteRange; import com.intellij.flex.uiDesigner.io.PrimitiveAmfOutputStream; import com.intellij.flex.uiDesigner.mxml.PropertyProcessor.PropertyKind; import com.intellij.javascript.flex.FlexReferenceContributor.StateReference; import com.intellij.javascript.flex.FlexStateElementNames; import com.intellij.lang.javascript.flex.AnnotationBackedDescriptor; import com.intellij.psi.PsiReference; import com.intellij.psi.xml.XmlAttribute; import com.intellij.psi.xml.XmlAttributeValue; import com.intellij.psi.xml.XmlTag; import gnu.trove.THashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * http://opensource.adobe.com/wiki/display/flexsdk/Enhanced+States+Syntax */ class StateWriter { int ITEMS_FACTORY; private int DESTINATION; private int RELATIVE_TO; private int POSITION; private int FIRST; private int AFTER; int VALUE; private int OVERRIDES; int NAME; int TARGET; private int STATIC_INSTANCE_REFERENCE_IN_DEFERRED_PARENT_INSTANCE; int ADD_ITEMS; private final ArrayList<State> states = new ArrayList<>(); private final Map<String, List<State>> nameToState = new THashMap<>(); private BaseWriter writer; private SetPropertyOrStyle pendingFirstSetProperty; private boolean autoItemDestruction; private boolean namesInitialized; public StateWriter(BaseWriter writer) { this.writer = writer; } int statesSize() { return states.size(); } private void initNames() { if (!namesInitialized) { ITEMS_FACTORY = writer.getNameReference("itemsFactory"); DESTINATION = writer.getNameReference("destination"); RELATIVE_TO = writer.getNameReference("relativeTo"); POSITION = writer.getNameReference("position"); FIRST = writer.getNameReference("first"); AFTER = writer.getNameReference("after"); NAME = writer.getNameReference("name"); TARGET = writer.getNameReference("target"); VALUE = writer.getNameReference("value"); OVERRIDES = writer.getNameReference("overrides"); STATIC_INSTANCE_REFERENCE_IN_DEFERRED_PARENT_INSTANCE = writer.getNameReference( "com.intellij.flex.uiDesigner.flex.states.StaticInstanceReferenceInDeferredParentInstance"); ADD_ITEMS = writer.getNameReference("com.intellij.flex.uiDesigner.flex.states.AddItems"); namesInitialized = true; } } public void readDeclaration(XmlTag parentTag) { initNames(); XmlTag[] tags = parentTag.getSubTags(); states.ensureCapacity(tags.length); for (XmlTag tag : tags) { State state = new State(this, states.size()); states.add(state); for (XmlAttribute attribute : tag.getAttributes()) { if (attribute.getLocalName().equals(FlexStateElementNames.NAME)) { state.name = attribute.getDisplayValue(); addNameToStateMap(state.name, state); } else if (attribute.getLocalName().equals(FlexStateElementNames.STATE_GROUPS)) { XmlAttributeValue valueElement = attribute.getValueElement(); assert valueElement != null; for (PsiReference reference : valueElement.getReferences()) { addNameToStateMap(reference.getCanonicalText(), state); } } } } } private void addNameToStateMap(String name, State state) { List<State> states = nameToState.get(name); if (states == null) { states = new ArrayList<>(5); states.add(state); nameToState.put(name, states); } else { if (states.contains(state)) { // IDEA-73547 MxmlWriter.LOG.warn("State " + state + " already added to map for " + name); } else { states.add(state); } } } public void applyItemAutoDestruction(@NotNull Context context, Context parentContext) { if (context instanceof NullContext) { autoItemDestruction = true; return; } for (State state : states) { state.applyItemAutoDestruction(context, parentContext, writer); } } public void includeInOrExcludeFrom(XmlAttributeValue xmlAttributeValue, Context parentContext, DynamicObjectContext context, boolean excludeFrom) { // currently, all references in includeIn/excludeFrom attribute value are StateReference, so, we skip instanceof StateReference final PsiReference[] references = xmlAttributeValue.getReferences(); assert context.includeInStates.isEmpty(); if (excludeFrom) { context.includeInStates.addAll(states); for (PsiReference reference : references) { context.includeInStates.removeAll(nameToState.get(reference.getCanonicalText())); } } else { for (PsiReference reference : references) { context.includeInStates.addAll(nameToState.get(reference.getCanonicalText())); } } includeIn(parentContext, context, context.includeInStates); autoItemDestruction = false; pendingFirstSetProperty = null; } private void includeIn(Context parentContext, DynamicObjectContext context, List<State> includedStates) { for (State state : includedStates) { // lazy reset state.activeAddItems AddItems override = state.getValidActiveAddItems(parentContext, autoItemDestruction); if (override != null) { override.addItemDeferredInstance(context); } else { state.addAddItems(createAddItems(context, parentContext, autoItemDestruction), parentContext, pendingFirstSetProperty); } } } AddItems createAddItems(DynamicObjectContext context, Context parentContext, boolean autoDestruction) { AddItems override = new AddItems(writer.getBlockOut().startRange(), context, autoDestruction); DynamicObjectContext parentScopeOwner = parentContext.getScope().getOwner(); if (parentScopeOwner != null && parentScopeOwner != parentContext) { writeDeferredInstanceFromObjectReference(DESTINATION, parentContext, parentContext); } else { writer.property(DESTINATION).objectReference(parentContext); } final int position; if (parentContext.getBackSibling() == null) { position = FIRST; } else { position = AFTER; if (parentContext.ownerIsDynamic()) { writeDeferredInstanceFromObjectReference(RELATIVE_TO, parentContext.getBackSibling(), parentContext); } else { writer.property(RELATIVE_TO).objectReference(parentContext.getBackSibling()); } } if (parentContext.processingPropertyName != null) { writer.property("propertyName").stringReference(parentContext.processingPropertyName); } writer.property(POSITION).stringReference(position); writer.getBlockOut().endRange(override.dataRange); return override; } public boolean checkStateSpecificPropertyValue(MxmlWriter mxmlWriter, PropertyProcessor propertyProcessor, XmlElementValueProvider valueProvider, AnnotationBackedDescriptor descriptor, @NotNull Context context) { PsiReference[] references = valueProvider.getElement().getReferences(); if (references.length < 2) { return false; } List<State> states = null; for (int i = references.length - 1; i > -1; i--) { PsiReference psiReference = references[i]; if (psiReference instanceof StateReference) { // resolve is expensive for StateReference, so, we use string key (states name) instead of object key (states tag) states = nameToState.get(psiReference.getCanonicalText()); break; } } if (states == null) { return false; } final List<State> filteredStates; if (context instanceof DynamicObjectContext) { final ArrayList<State> includeInStates = ((DynamicObjectContext)context).includeInStates; filteredStates = new ArrayList<>(states.size()); for (State state : states) { if (includeInStates.contains(state)) { filteredStates.add(state); } else { MxmlWriter.LOG.warn("Skip " + valueProvider.getElement().getText() + " from " + state.name + " " + "due to element parent object included only in " + includeInStates); } } if (filteredStates.isEmpty()) { return true; } } else { filteredStates = states; } ValueWriter valueWriter = null; try { valueWriter = propertyProcessor.process(valueProvider.getElement(), valueProvider, descriptor, context); } catch (InvalidPropertyException ignored) { } if (valueWriter == null) { // binding is not yet supported for state specific return true; } final PrimitiveAmfOutputStream out = writer.getOut(); SetPropertyOrStyle override = new SetPropertyOrStyle(writer.getBlockOut().startRange()); writer.classOrPropertyName(propertyProcessor.isStyle() ? "com.intellij.flex.uiDesigner.flex.states.SetStyle" : "com.intellij.flex.uiDesigner.flex.states.SetProperty"); writer.property(NAME).stringReference(propertyProcessor.getName()); writer.property(VALUE); PropertyKind propertyKind; try { propertyKind = valueWriter.write(descriptor, valueProvider, out, writer, false, context); } catch (InvalidPropertyException ignored) { // todo handle invalidProperty for state throw new UnsupportedOperationException(""); } if (propertyKind.isComplex()) { mxmlWriter.processPropertyTagValue(descriptor, (XmlTag)valueProvider.getElement(), context, propertyKind); } override.targetId = context.getOrAllocateId(); if (pendingFirstSetProperty == null && context instanceof NullContext) { pendingFirstSetProperty = override; } writer.getBlockOut().endRange(override.dataRange); for (State state : filteredStates) { state.overrides.add(override); } return true; } public StaticObjectContext createContextForStaticBackSibling(boolean allowIncludeInExcludeFrom, int referencePosition, @Nullable Context parentContext) { assert referencePosition != -1; if (allowIncludeInExcludeFrom) { assert parentContext != null; int backSiblingId = (writer.isIdPreallocated() && !parentContext.ownerIsDynamic()) ? writer.getPreallocatedId() : -1; // reset due to new backsibling resetActiveAddItems(parentContext.activeAddItems); if (parentContext.getBackSibling() == null) { StaticObjectContext sibling = new StaticObjectContext(referencePosition, writer.getOut(), backSiblingId, parentContext.getScope()); parentContext.setBackSibling(sibling); } else { parentContext.getBackSibling().reinitialize(referencePosition, backSiblingId); resetActiveAddItems(parentContext.getBackSibling().activeAddItems); } return parentContext.getBackSibling(); } else { return writer.createStaticContext(parentContext, referencePosition); } } public void finalizeStateSpecificAttributesForStaticContext(@NotNull StaticObjectContext context, @Nullable Context parentContext, MxmlWriter mxmlWriter) { // 1 if (!writer.isIdPreallocated()) { assert pendingFirstSetProperty == null; return; } if (parentContext == null || !parentContext.ownerIsDynamic()) { pendingFirstSetProperty = null; return; } final int objectInstance = parentContext.getScope().referenceCounter++; final int deferredParentInstance = parentContext.getScope().getOwner().getOrAllocateId(); final int referenceInstance = writer.getPreallocatedId(); // 2 if (pendingFirstSetProperty != null) { ByteRange byteRange = writer.getBlockOut().startRange(); writeDeferredInstanceFromObjectReference(TARGET, objectInstance, deferredParentInstance, referenceInstance); writer.getBlockOut().endRange(byteRange); assert pendingFirstSetProperty.targetId == referenceInstance; pendingFirstSetProperty.setTargetRange(byteRange); pendingFirstSetProperty = null; } else { // 3 StaticInstanceReferenceInDeferredParentInstance staticInstanceReferenceInDeferredParentInstance = new StaticInstanceReferenceInDeferredParentInstance(objectInstance, deferredParentInstance); mxmlWriter.setDeferredReferenceForObjectWithExplicitIdOrBinding(staticInstanceReferenceInDeferredParentInstance, referenceInstance); context.setStaticInstanceReferenceInDeferredParentInstance(staticInstanceReferenceInDeferredParentInstance); } context.setId(objectInstance); context.referenceInitialized(); context.setId(referenceInstance); } private static void resetActiveAddItems(AddItems[] activeAddItems) { if (activeAddItems != null) { for (int i = 0; i < activeAddItems.length; i++) { activeAddItems[i] = null; } } } private void writeDeferredInstanceFromObjectReference(int propertyName, Context context, Context parentContext) { int referenceInstanceReference = context.getId(); if (referenceInstanceReference == -1) { int objectInstanceReference = parentContext.getScope().referenceCounter++; context.setId(objectInstanceReference); context.referenceInitialized(); referenceInstanceReference = writer.allocateAbsoluteStaticObjectId(); context.setId(referenceInstanceReference); writeDeferredInstanceFromObjectReference(propertyName, objectInstanceReference, parentContext.getScope().getOwner().getOrAllocateId(), referenceInstanceReference); } else { StaticInstanceReferenceInDeferredParentInstance referenceInDeferredParentInstance = context.getStaticInstanceReferenceInDeferredParentInstance(); if (referenceInDeferredParentInstance == null) { writer.property(propertyName).objectReference(referenceInstanceReference); } else { writeDeferredInstanceFromObjectReference(propertyName, referenceInDeferredParentInstance.getObjectInstance(), referenceInDeferredParentInstance.getDeferredParentInstance(), referenceInstanceReference); referenceInDeferredParentInstance.markAsWritten(); context.setStaticInstanceReferenceInDeferredParentInstance(null); } } } private void writeDeferredInstanceFromObjectReference(int propertyName, int objectInstanceReference, int deferredParentInstance, int referenceInstanceReference) { writer.property(propertyName).referableHeader(referenceInstanceReference).objectHeader( STATIC_INSTANCE_REFERENCE_IN_DEFERRED_PARENT_INSTANCE); writer.property("reference").getOut().writeAmfInt(objectInstanceReference); writer.property("deferredParentInstance").objectReference(deferredParentInstance); writer.endObject(); } void writeDeferredInstance(DynamicObjectContext instance) { PrimitiveAmfOutputStream out = writer.getOut(); if (instance.isWritten()) { writeDeferredInstanceKind(AmfExtendedTypes.OBJECT_REFERENCE, instance); out.writeUInt29(instance.id); } else { // IDEA-72004 if (instance.overrideUserCount > 0) { instance.getOrAllocateId(); } if (instance.id == -1) { writeDeferredInstanceKind(ObjectMetadata.NEVER_REFERRED, instance); } else { writeDeferredInstanceKind(ObjectMetadata.REFERRED, instance); instance.markAsWritten(); } out.writeUInt29(BlockDataOutputStream.getDataRangeOwnLength(instance.getDataRange()) + 2); out.writeShort(instance.getReferredObjectsCount()); writer.getBlockOut().append(instance.getDataRange()); if (instance.id != -1) { out.writeUInt29(instance.id); } } } private void writeDeferredInstanceKind(int kind, DynamicObjectContext instance) { writer.getOut().write((kind << 1) | (instance.isImmediateCreation() ? 1 : 0)); } public void write() { final PrimitiveAmfOutputStream out = writer.getOut(); out.write(states.size()); if (states.isEmpty()) { return; } for (State state : states) { writer.property(NAME).string(state.name); if (!state.overrides.isEmpty()) { writer.property(OVERRIDES).arrayHeader(state.overrides.size()); for (OverrideBase override : state.overrides) { override.write(writer, this); } } // object State footer writer.endObject(); } reset(); } public void reset() { states.clear(); nameToState.clear(); namesInitialized = false; pendingFirstSetProperty = null; } }