Как стать автором
Обновить

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

Время на прочтение4 мин
Количество просмотров1.6K
Привет, Хабр!

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

Спасибо всем за внимание, буду рад услышать пожелания, о чем написать в следующих статьях цикла!
Теги:
Хабы:
Всего голосов 22: ↑22 и ↓0+22
Комментарии5

Публикации