MXML компилятор. Часть 2. Не строковые инициализаторы параметров

    Привет, Хабр!

    В предыдущей статье я провёл некий ликбез по внутренностям 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 времени ушло на определение, куда что впихнуть вписать. Не бойтесь копаться в opensource проектах, пытаться модифицировать их и сделать лучше, особенно, если Вам приходится с ними работать.

    Спасибо всем за внимание, буду рад услышать пожелания, о чем написать в следующих статьях цикла!

    Similar posts

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 5

      0
      Спасибо тебе огромное. Думаю, мало кто бы сам полез разбираться на подобном уровне. Но это очень полезно.
        +2
        Тема сама по себе несколько странная.
        Использовать это в промышленных масштабах нереально. Ну, т.е. можно, конечно, либо отказавшись от использования следующих версий, либо перетаскивая изменения из версии в версию.
        Это главный стоп.
        Второй стоп — проблема расшаривания таких изменений. А еще больше — проблема склеивания нескольких разных изменений от разных авторов.

        В итоге, это тупиковый путь создания DSL. Но + за попытку! :)
          0
          Но ведь это опенсорс! Я не в курсе легко ли там стать коммитером, но это решение можно хотя бы показать на профильном форуме. Авось, включат.
            0
            issues.apache.org/jira/browse/FLEX-33120

            Вот такой он, opensource :)

            А если б Вы читали, что там в mail-подписке творилось в обсуждении этой таски, там и вопросы морали затронули, и прочее) Посмеялись вдоволь, в итоге — неформальный Reject.
            0
            Про обсуждения тасков:
            github.com/PrimaryFeather/Starling-Framework/issues/253
            таки заспамили)

            Only users with full accounts can post comments. Log in, please.