package com.intellij.flex.uiDesigner.libraries; import com.intellij.flex.uiDesigner.abc.AbcModifierBase; import com.intellij.flex.uiDesigner.abc.AbcUtil; import com.intellij.flex.uiDesigner.abc.DataBuffer; import com.intellij.flex.uiDesigner.abc.Encoder; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.text.CharArrayUtil; import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharsetEncoder; import java.nio.charset.CodingErrorAction; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import static com.intellij.flex.uiDesigner.abc.Encoder.SkipMethodKey; class FlexDefinitionProcessor implements DefinitionProcessor { private static final String MX_CORE = "mx.core:"; private static final String SPARK_COMPONENTS = "spark.components:"; static final String[] OVERLOADED = new String[]{ "mx.styles:StyleProtoChain", "spark.components.supportClasses:SkinnableComponent", "mx.effects:Effect", "spark.modules:ModuleLoader", "mx.controls:SWFLoader", }; static final char OVERLOADED_AND_BACKED_CLASS_MARK = 'F'; private final boolean vGreaterOrEquals4_5; public FlexDefinitionProcessor(String version) { vGreaterOrEquals4_5 = StringUtil.compareVersionNumbers(version, "4.5") >= 0; } @Override public void process(CharSequence name, ByteBuffer buffer, Definition definition, Map<CharSequence, Definition> definitionMap) throws IOException { for (String overloadedClassName : OVERLOADED) { if (StringUtil.equals(name, overloadedClassName)) { changeAbcName(overloadedClassName, buffer); flipDefinition(definition, definitionMap, overloadedClassName); } } if (StringUtil.equals(name, "mx.containers:Panel")) { final List<SkipMethodKey> skippedMethods; if (vGreaterOrEquals4_5) { skippedMethods = new ArrayList<>(1); skippedMethods.add(new SkipMethodKey("addChildAt", true)); } else { skippedMethods = null; } definition.doAbcData.abcModifier = new MethodAccessModifier("setControlBar", skippedMethods); } else if (StringUtil.equals(name, "mx.controls:SWFLoader")) { definition.doAbcData.abcModifier = new MethodAccessModifier("loadContent", null); } else if (StringUtil.equals(name, "mx.styles:StyleProtoChain")) { List<String> list = new ArrayList<>(2); list.add("matchStyleDeclarations"); list.add("sortOnSpecificity"); definition.doAbcData.abcModifier = new MethodAccessModifier(list); } else { final boolean mxCore = StringUtil.startsWith(name, MX_CORE); if (mxCore) { if (equals(name, MX_CORE.length(), "UIComponent")) { List<SkipMethodKey> list = new ArrayList<>(1); list.add(new SkipMethodKey("removedFromStageHandler", false, true)); definition.doAbcData.abcModifier = new MethodAccessModifier("UIComponent", list, new VarAccessModifier("deferredSetStyles")); } else if (vGreaterOrEquals4_5 && equals(name, MX_CORE.length(), "FTETextField")) { definition.doAbcData.abcModifier = new VarAccessModifier("staticHandlersAdded"); } } if (definition.doAbcData.abcModifier != null) { return; } boolean skipInitialize = false; // Application without explicit size hangs on Stage and listen to resize - but we must set size via setLayoutBoundsSize boolean skipCommitProperties = false; boolean modifyConstructor = false; boolean skipColorCorrection = false; if (mxCore || StringUtil.startsWith(name, SPARK_COMPONENTS)) { final int localNameOffset = mxCore ? MX_CORE.length() : SPARK_COMPONENTS.length(); skipInitialize = equals(name, localNameOffset, "Application"); skipCommitProperties = skipInitialize; if (mxCore) { if (skipInitialize) { modifyConstructor = true; } } else { skipColorCorrection = skipInitialize && vGreaterOrEquals4_5; modifyConstructor = skipInitialize || equals(name, localNameOffset, "ViewNavigatorApplicationBase") || equals(name, localNameOffset, "TabbedViewNavigatorApplication"); // AS-66 if (!skipInitialize && vGreaterOrEquals4_5) { skipInitialize = equals(name, localNameOffset, "View"); } } } else if (vGreaterOrEquals4_5 && StringUtil.equals(name, "spark.components.supportClasses:ViewNavigatorApplicationBase")) { skipInitialize = true; } final List<SkipMethodKey> skippedMethods; if (skipInitialize || skipCommitProperties) { skippedMethods = new ArrayList<>(2); skippedMethods.add(new SkipMethodKey("initialize", true)); if (skipCommitProperties) { skippedMethods.add(new SkipMethodKey("commitProperties", false)); } } else { skippedMethods = null; } if (skippedMethods != null || modifyConstructor) { definition.doAbcData.abcModifier = new MethodAccessModifier(skippedMethods, skipColorCorrection ? "colorCorrection" : null, modifyConstructor, null, null); } } } private static boolean equals(CharSequence s1, int s1Offset, CharSequence s2) { if ((s1.length() - s1Offset) != s2.length()) { return false; } for (int i = 0; i < s2.length(); i++) { if (s1.charAt(i + s1Offset) != s2.charAt(i)) { return false; } } return true; } private static void flipDefinition(Definition definition, Map<CharSequence, Definition> definitionMap, String name) { // don't remove old entry from map, it may be required before we inject int i = name.indexOf(':'); String newName = name.substring(0, i + 1) + OVERLOADED_AND_BACKED_CLASS_MARK + name.substring(i + 2); definitionMap.put(newName, definition); //definition.name = newName; } private static void changeAbcName(final String name, ByteBuffer buffer) { final int oldPosition = buffer.position(); buffer.position(buffer.position() + 4 + name.length() + 1 /* null-terminated string */); parseCPoolAndRename(name.substring(name.indexOf(':') + 1), buffer); // modify abcname buffer.position(oldPosition + 4 + 10); buffer.put((byte)OVERLOADED_AND_BACKED_CLASS_MARK); buffer.position(oldPosition); } private static void parseCPoolAndRename(String from, ByteBuffer buffer) { buffer.position(buffer.position() + 4); int n = AbcUtil.readU32(buffer); while (n-- > 1) { AbcUtil.readU32(buffer); } n = AbcUtil.readU32(buffer); while (n-- > 1) { AbcUtil.readU32(buffer); } n = AbcUtil.readU32(buffer); if (n != 0) { buffer.position(buffer.position() + ((n - 1) * 8)); } n = AbcUtil.readU32(buffer); final CharsetEncoder charsetEncoder = StandardCharsets.UTF_8.newEncoder().onMalformedInput(CodingErrorAction.REPLACE).onUnmappableCharacter( CodingErrorAction.REPLACE); o: while (n-- > 1) { int l = AbcUtil.readU32(buffer); buffer.limit(buffer.position() + l); buffer.mark(); final CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer); buffer.limit(buffer.capacity()); int index = 0; do { index = CharArrayUtil.indexOf(charBuffer, from, index); if (index == -1 || (index > 0 && !isSpecialSymbol(charBuffer.get(index - 1)))) { continue o; } charBuffer.put(index, OVERLOADED_AND_BACKED_CLASS_MARK); index += from.length(); } while (index < charBuffer.length()); buffer.reset(); charsetEncoder.encode(charBuffer, buffer, true); charsetEncoder.reset(); } } // E:\dev\4.5.1\frameworks\projects\spark\src;spark\components\supportClasses;SkinnableComponent.as // spark.components.supportClasses:SkinnableComponent/SkinnableComponent private static boolean isSpecialSymbol(char c) { return c == ';' || c == '/' || c == ':'; } private static class MethodAccessModifier extends AbcModifierBase { private List<String> changeAccessModifier; private List<SkipMethodKey> skippedMethods; private String skipSetter; private boolean modifyConstructor; private final int traitDelta; private final VarAccessModifier varAccessModifier; private final String classLocalName; private MethodAccessModifier(List<String> changeAccessModifier) { this(null, null, false, null, null); this.changeAccessModifier = changeAccessModifier; } private MethodAccessModifier(String classLocalName, List<SkipMethodKey> skippedMethods, VarAccessModifier varAccessModifier) { this(skippedMethods, null, false, varAccessModifier, classLocalName); } private MethodAccessModifier(String changeAccessModifier, List<SkipMethodKey> skippedMethods) { this(skippedMethods, null, false, null, null); this.changeAccessModifier = Collections.singletonList(changeAccessModifier); } private MethodAccessModifier(@Nullable List<SkipMethodKey> skippedMethods, @Nullable String skipSetter, boolean modifyConstructor, @Nullable VarAccessModifier varAccessModifier, @Nullable String classLocalName) { this.changeAccessModifier = null; this.skippedMethods = skippedMethods; this.skipSetter = skipSetter; this.modifyConstructor = modifyConstructor; traitDelta = computeTraitDelta(); this.varAccessModifier = varAccessModifier; this.classLocalName = classLocalName; } private int computeTraitDelta() { int traitDelta = 0; if (skippedMethods != null) { for (SkipMethodKey key : skippedMethods) { if (!key.clear) { traitDelta--; } } } return traitDelta; } @Override public String getClassLocalName() { return classLocalName; } @Override public int instanceMethodTraitDelta() { assert skippedMethods == null || traitDelta == computeTraitDelta(); return traitDelta; } @Override public boolean slotTraitName(int name, int traitKind, DataBuffer in, Encoder encoder) { return varAccessModifier != null && varAccessModifier.slotTraitName(name, traitKind, in, encoder); } @Override public boolean methodTraitName(int name, int traitKind, DataBuffer in, Encoder encoder) { if (changeAccessModifier != null && !changeAccessModifier.isEmpty() && isNotOverridenMethod(traitKind)) { for (int i = 0, size = changeAccessModifier.size(); i < size; i++) { String mName = changeAccessModifier.get(i); if (encoder.changeAccessModifier(mName, name, in)) { if (changeAccessModifier.size() == 1) { changeAccessModifier = null; } else { changeAccessModifier.remove(i); } return true; } } } return false; } @Override public boolean methodTrait(int traitKind, int name, DataBuffer in, int methodInfo, Encoder encoder) { if (skippedMethods != null && !skippedMethods.isEmpty()) { int index; if ((index = encoder.skipMethod(skippedMethods, name, in, methodInfo)) != -1) { if (skippedMethods.size() == 1) { skippedMethods = null; } else { skippedMethods.remove(index); } return index != -2; } } if (skipSetter != null && isSetter(traitKind) && encoder.clearMethodBody(skipSetter, name, in, methodInfo)) { skipSetter = null; // false, clearMethodBody just put it to map for deferred processing return false; } return false; } @Override public boolean isModifyConstructor() { if (!modifyConstructor) { return false; } modifyConstructor = false; return true; } @Override public void assertOnInstanceEnd() { assert skippedMethods == null; } } private static class VarAccessModifier extends AbcModifierBase { private final String[] fieldNames; private VarAccessModifier(String ...fieldNames) { this.fieldNames = fieldNames; } @Override public boolean slotTraitName(int name, int traitKind, DataBuffer in, Encoder encoder) { for (int i = 0, length = fieldNames.length; i < length; i++) { String fieldName = fieldNames[i]; if (fieldName != null && isVar(traitKind)) { if (encoder.changeAccessModifier(fieldName, name, in)) { fieldNames[i] = null; return true; } } } return false; } @Override public void assertOnInstanceEnd() { } } }