Привет, Хабр!
В предыдущей статье я провёл некий ликбез по внутренностям Flex-компилятора (по части MXML) и рассказывал, как избавить себя от проблем работы с классами в MXML, требующими параметры конструктора. Сейчас мы разберём другую сторону вопроса — установка параметров в custom-значения (к примеру, константы) без использования механизма Binding (хотя, очень похоже на него).
Я думаю каждый, кто так или иначе работал с Flex-ом, сталкивался примерно с такой конструкцией:
Если посмотреть на код, который генерирует mxmlc, то выглядеть он будет примерно так:
Не многовато ли кода для такой тривиальной задачи указания свойства константой, которая по определению не меняется?
Наша цель — сделать обратно совместимую модификацию, которая поддерживала бы autocomplete в цивильных IDE, плюс была бы визуально привычна Flex-программистам. Учитывая все перечисленные требования, решил, что решение будет выглядеть так:
Идея использовать символ доллара пришла из ANT-скриптов, удобно и понятно, а так же уже знакомо доброй половине флешеров.
Признаюсь честно, процесс я начал с поиска по всем классам в пакете java.flex2.compiler.mxml.lang (почему именно в нём? Смотрите первую статью из цикла ;)) на предмет вхождения строки "@{", т.к. именно с неё начинаются так называемые two-way bindings, и наша реализация могла бы отталкиваться именно от них. И мне повезло! Сразу же был обнаружен метод parseBindingExpression в java.flex2.compiler.mxml.lang.TextParser.
Для обозначения выражения Binding-а используются объекты типа BindingExpression, поэтому, по аналогии, создадим рядом с ним (в пакете flex2.compiler.mxml.rep) класс ExactValueExpression:
Мы будем использовать его в нашем собственном методе класса java.flex2.compiler.mxml.lang.TextParser, который я решил назвать parseExactValueExpression:
Который будем вызывать ПЕРЕД вызовом parseBindingExpression:
Теперь наша конструкция правильно парсится, осталось лишь её обработать. Идём в уже знакомый нам по предыдущей статье java.flex2.compiler.mxml.rep.init.ValueInitializer, метод formatExpr. Добавим в него 3 строчки:
Собираем проект коммандой ant compiler, ждём BUILD SUCCESSFUL (если вы не получили такого результата, проверяйте все шаги с начала).
Давайте изменим наш исходный пример, добавив символ доллара:
Если вы всё сделали правильно, то результат будет примерно таким:
Данную модификацию я провёл сегодня за конец обеденного перерыва (примерно 20-30 минут), из которых примерно 3\4 времени ушло на определение, куда чтовпихнуть вписать. Не бойтесь копаться в opensource проектах, пытаться модифицировать их и сделать лучше, особенно, если Вам приходится с ними работать.
Спасибо всем за внимание, буду рад услышать пожелания, о чем написать в следующих статьях цикла!
В предыдущей статье я провёл некий ликбез по внутренностям Flex-компилятора (по части MXML) и рассказывал, как избавить себя от проблем работы с классами в MXML, требующими параметры конструктора. Сейчас мы разберём другую сторону вопроса — установка параметров в custom-значения (к примеру, константы) без использования механизма Binding (хотя, очень похоже на него).
Вступительное слово
Я заранее хочу извиниться за то, что эта статья вышла позже, чем я говорил — буквально на следующий же день после статьи меня взяли на работу, и времени попросту небыло:) Сейчас всё разрешилось, и я готов продолжить цикл статей.Я думаю каждый, кто так или иначе работал с Flex-ом, сталкивался примерно с такой конструкцией:
<TextField xmlns="flash.text.*" autoSize="{TextFieldAutoSize.CENTER}" />
Если посмотреть на код, который генерирует mxmlc, то выглядеть он будет примерно так:
private function _MyOwnFlexFrameworkTest_TextField1_i() : flash.text.TextField { var temp : flash.text.TextField = new flash.text.TextField(); _MyOwnFlexFrameworkTest_TextField1 = temp; mx.binding.BindingManager.executeBindings(this, "_MyOwnFlexFrameworkTest_TextField1", _MyOwnFlexFrameworkTest_TextField1); return temp; } // binding mgmt private function _MyOwnFlexFrameworkTest_bindingsSetup():Array { var result:Array = []; result[0] = new mx.binding.Binding(this, function():String { var result:* = (TextFieldAutoSize.CENTER); return (result == undefined ? null : String(result)); }, null, "_MyOwnFlexFrameworkTest_TextField1.autoSize" ); return result; }
Не многовато ли кода для такой тривиальной задачи указания свойства константой, которая по определению не меняется?
Let's do it!
Наша цель — сделать обратно совместимую модификацию, которая поддерживала бы autocomplete в цивильных IDE, плюс была бы визуально привычна Flex-программистам. Учитывая все перечисленные требования, решил, что решение будет выглядеть так:
<TextField xmlns="flash.text.*" autoSize="${TextFieldAutoSize.CENTER}" />
Идея использовать символ доллара пришла из ANT-скриптов, удобно и понятно, а так же уже знакомо доброй половине флешеров.
Признаюсь честно, процесс я начал с поиска по всем классам в пакете java.flex2.compiler.mxml.lang (почему именно в нём? Смотрите первую статью из цикла ;)) на предмет вхождения строки "@{", т.к. именно с неё начинаются так называемые two-way bindings, и наша реализация могла бы отталкиваться именно от них. И мне повезло! Сразу же был обнаружен метод parseBindingExpression в java.flex2.compiler.mxml.lang.TextParser.
Для обозначения выражения Binding-а используются объекты типа BindingExpression, поэтому, по аналогии, создадим рядом с ним (в пакете flex2.compiler.mxml.rep) класс ExactValueExpression:
package flex2.compiler.mxml.rep; public class ExactValueExpression { /** The source expression for this value */ private String exactValue; public ExactValueExpression(String exactValueExpression) { this.exactValue = exactValueExpression; } public String getValueExpression() { return exactValue; } }
Мы будем использовать его в нашем собственном методе класса java.flex2.compiler.mxml.lang.TextParser, который я решил назвать parseExactValueExpression:
/** * @param s the string to be parsed * @return ExactValueExpression or null */ protected ExactValueExpression parseExactValueExpression(String s) { int dollarIdx; int openBraceIdx = -1; dollarIdx = StringUtils.findNextUnescaped('$', 0, s); if (dollarIdx == -1) { // String doesn't start with "$" return null; } openBraceIdx = StringUtils.findNextUnescaped('{', dollarIdx + 1, s); if (openBraceIdx != dollarIdx + 1) { // open bracet not in place return null; } int closeBraceIdx = StringUtils.findClosingToken('{', '}', s, openBraceIdx); if (closeBraceIdx == -1) { return null; } String contents = s.substring(openBraceIdx + 1, closeBraceIdx); if (contents.length() == 0) { // Convert ${} to null contents = "null"; } //Don't include the braces (or parens since they will just get stripped). return new ExactValueExpression( contents ); }
Который будем вызывать ПЕРЕД вызовом parseBindingExpression:
protected Object parse(String text, Type type, Type arrayElementType, int flags) { if (!inCDATA(flags)) { ExactValueExpression exactValueExpression = parseExactValueExpression(text); if(exactValueExpression != null) { return exactValueExpression; } // binding? if (!ignoreBinding(flags)) { BindingExpression result = parseBindingExpression(text); if (result != null) { return result; } else { text = cleanupBindingEscapes(text); } }
Теперь наша конструкция правильно парсится, осталось лишь её обработать. Идём в уже знакомый нам по предыдущей статье java.flex2.compiler.mxml.rep.init.ValueInitializer, метод formatExpr. Добавим в него 3 строчки:
if(value instanceof ExactValueExpression) { return ((ExactValueExpression) value).getValueExpression(); }
Собираем проект коммандой ant compiler, ждём BUILD SUCCESSFUL (если вы не получили такого результата, проверяйте все шаги с начала).
Результат
Давайте изменим наш исходный пример, добавив символ доллара:
<TextField xmlns="flash.text.*" autoSize="${TextFieldAutoSize.CENTER}" />
Если вы всё сделали правильно, то результат будет примерно таким:
private function _MyOwnFlexFrameworkTest_TextField1_i() : flash.text.TextField { var temp : flash.text.TextField = new flash.text.TextField(); temp.autoSize = TextFieldAutoSize.CENTER; _MyOwnFlexFrameworkTest_TextField1 = temp; mx.binding.BindingManager.executeBindings(this, "_MyOwnFlexFrameworkTest_TextField1", _MyOwnFlexFrameworkTest_TextField1); return temp; }
Заключение
Данную модификацию я провёл сегодня за конец обеденного перерыва (примерно 20-30 минут), из которых примерно 3\4 времени ушло на определение, куда что
Спасибо всем за внимание, буду рад услышать пожелания, о чем написать в следующих статьях цикла!