19 сентября 2011 в 13:31

Добавляем в Flex-компилятор MXML параметры конструктора из песочницы

26 Апреля 2007 года. Adobe делает заявление, что Flex Framework переводят на open source. Данная новость вызвала бурю оваций среди флешеров всего мира, что появятся принципиально новые фреймворки на его основе, но дальше пары-тройки проектов это не пошло.

В то время я делал проекты с использованием Flex-а, и чем больше проекты становились, тем сильнее мне не нравилось некоторые нюансы его внутреннего устройства.
Думаю, каждому, кто работал с Flex-ом, известны его проблемы, а так же низкое качество кода как уже написанного, так и генерируемого из MXML, поэтому я решил вмешаться в этот процесс с целью навести порядок и разобраться в устройстве Flex компилятора, и начал с добавления «сахара» в MXML, стараясь сделать это как можно сильнее в духе остальных фич детища Adobe-а. Спустя два года, я принял решение поделиться своими знаниями с Вами.

Предисловие


Я не буду приводить описание процесса чекаута проекта FlexSDK из SVN репозитория, лишь оставлю ссылку на описание данного процесса

На момент написания статьи последняя версия была Flex 4.5.1 (4.5.1.21328), поэтому отталкиваться стоит от неё.

Когда я только начал копать компилятор у меня не было достаточных знаний Java, поэтому приходилось руководствоваться здравым смыслом, все изменения проводились в Notepad++.
Вам же советую использовать вашу любимую Java IDE, потому что без блекджека и шлюх «Goto Declaration» и «Find Usages» было очень и очень тяжело.

Что внутри


К сожалению, одной статьи не хватит для покрытия устройства всего компилятора, поэтому в этой я расскажу лишь о малой его части — транслятора MXML.
Все исходники, относящиеся к нему, находятся в папке modules/compiler/src/java/flex2/compiler/mxml:



  • пакет analyzer, как не странно, отвечает за анализ распарсенного XML в виде Token-ов, наибольшее внимание привлекает SyntaxAnalyzer. На этом этапе не происходит никаких кодогенераций, лишь анализ полученной модели из MXML;
  • пакет builder — наиболее интересный для нас (в рамках этой статьи), содержит набор Builder-ов, преобразующих полученные Token-ы в объектную модель AS3. На диаграмме изображены компоненты пакета:

    Вкратце:
    1. AbstractBuilder — базовый класс для всех остальных, содержит логику генерирования объекта из полученного Token-а;
    2. XMLListBuilder, XMLBuilder, ArrayBuilder, VectorBuilder — билдеры для одноимённых классов AS3;
    3. InlineComponentBuilder обрабатывает «компоненты внутри компонент», т.е. те, что образуются при написании <fx:Component></fx:Component>;
    4. PrimitiveBuilder отвечает за языковые примитивы, такие как String, Number, int, uint, Boolean;
    5. ModelBuilder, AnonymousObjectGraphBuilder и ServiceRequestBuilder позволяют создать объект из <fx:Model /> и подобных;
    6. ComponentBuilder — крайне важный класс, т.к. он содержит логику создания объекта из любого MXML-тега, не подходящего под приведённые выше категории. Используется нами чаще всего, т.к. именно он создаёт объект из конструкций вида <s:Button/>, <s:List/>, <someNamespace:SomeObject/>;
    7. DocumentBuilder представляет собой root-тег, как, к примеру, <s:Application />, или любой другой;
    8. HTTPServiceBuilder, RemoteObjectBuilder и WebServiceBuilder — билдеры соответствующих тегов;
  • пакет dom содержит объявления Node-ов для каждого из встречаемых типов тегов MXML. Кому интересно, выкладываю картинку с наглядным представлением структуры пакета;
  • пакет gen содержит шаблоны velocity для кодогенерации AS3 + вспомогательные классы для этого. Используется только в случае указания -keep-generated-actionscript, в ином случае происходит генерация сразу в AbstractSyntaxTree;
  • пакет lang содержит константы, которые относятся к специфике языка, а так же handler-ы для аттрибутов, биндингов, и прочего. Советую обратить внимание на StandardDefs;
  • пакет reflect содержит интерфейсы Reflection API и класс TypeTable с приватной реализацией каждого из них;
  • пакет rep — это инициализаторы для каждого из node-ов MXML документа. Структура:


От теории к практике!


Сам не ожидал, что так много получится «теории», но это сильно поможет Вам понять, где, что и зачем. А теперь перейдём непосредственно к тому, ради чего всё затевалось — возможности указывать для MXML объектов параметры конструктора.

Много классов в AS3 имеют обязательные параметры конструктора, из-за которых эти классы приходится оборачивать в объекты без параметров, либо создавать из в теге <fx:Script />, что тоже нарушает наш привычный вид MXML, поэтому я предлагаю сделать вот такую вот реализацию:
	<?xml version="1.0"?>
	<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
	               xmlns:s="library://ns.adobe.com/flex/spark">
	    <fx:Declarations>
	        <Timer xmlns="flash.utils.*" new="1000, 1" />
	    </fx:Declarations>
	</s:Application>
	


Выбор ключевого слова new обусловлен тем, что в AS3 мы никогда не сможем создать свойство с именем new, т.к. оно зарезервированно. То, что переданно в new, будет подставляться в скобки при инициализации нашего объекта, в данном случае,
new Timer(1000, 1);


Проходим валидацию

Чтобы валидатор пропустил наш новый аттрибут, мы должны добавить его к таким же (к примеру, includeIn, excludeFrom, и так далее). Текстовый поиск показал, что сначала этот аттрибут объявляют в lang/StandardDefs.java, а потом сверяют в builder/ComponentBuilder.java (метод isSpecialAttribute)

добавляем в StandardDefs
public static final String PROP_NEW = "new";

после 310 строки:
public static final String PROP_ID = "id";


А так же в ComponentBuilder (метод isSpecialAttribute)
	if (namespace.length() == 0 || 
	                namespace.equals(document.getLanguageNamespace())) 
	            {
	                if (StandardDefs.PROP_ID.equals(localPart) ||
	                    StandardDefs.PROP_NEW.equals(localPart) ||
	                    StandardDefs.PROP_INCLUDE_STATES.equals(localPart) || 
	                    StandardDefs.PROP_EXCLUDE_STATES.equals(localPart) ||
	                    StandardDefs.PROP_ITEM_CREATION_POLICY.equals(localPart) ||
	                    StandardDefs.PROP_ITEM_DESTRUCTION_POLICY.equals(localPart))
	                {
	                    isSpecial = true;
	                }
	            }
	

Теперь валидатор не ругается на аттрибут new у MXML-тегов.

Регистрируем новое свойство у модели объекта

Итак, наш аттрибут имеет место быть, но надо же где-то сохранить его значение? Для этого у нас есть Model.java.
Создаём в rep/Model.java getter/setter-ы для параметров конструктора, добавив в класс свойство и два метода:
	protected String constructorParams;
	    
	    public void setConstructorParams(String value)
	    {
	        constructorParams = value;
	    }
	    
	    public String getConstructorParams()
	    {
	        return constructorParams;
	    }
	


Это свойство мы будем использовать в методе registerModel(Node node, Model model, boolean topLevel) класса AbstractBuilder.java (строка 1617):
	    protected void registerModel(Node node, Model model, boolean topLevel)
	    {
	        String id = (String)getLanguageAttributeValue(node, StandardDefs.PROP_ID);
	        String newValue = (String)getLanguageAttributeValue(node, StandardDefs.PROP_NEW);
	        // get the comment from the node and store in the model
	        if(node.comment != null) 
	        {
	            // if generate ast if false, lets not scan the tokens here because they will be scanned later in asc scanner. 
	            // we will go the velocity template route
	            if(!mxmlConfiguration.getGenerateAbstractSyntaxTree())
	            {
	                model.comment = node.comment;
	            }
	            else
	            {
	                model.comment = MxmlCommentUtil.commentToXmlComment(node.comment);   
	            }
	        }

	        if(newValue != null)
	        {
	            model.setConstructorParams(newValue);
	        }
	        
	        registerModel(id, model, topLevel);
	    }
	


Используем аттрибут в кодогенерации

Осталась самая малая часть — добавить наш аттрибут в место, где создаётся объект, передав его AS IS в скобки конструктора. Сказано — сделанно!

Класс rep/init/ValueInitializer.java, метод getDefinitionBody(), сразу после Adobe-вской TODO на 467 строке:
	        else
	        {
	            // TODO confirm the availability of a 0-arg ctor!! but do it upstream from here, like when Model is built
	            stringBuilder.append("new " + selfTypeName + "(");
	            if(self.getConstructorParams() != null)
	            {
	                 stringBuilder.append(self.getConstructorParams());
	            }
	            stringBuilder.append(")");
	        }

	


Компиляция

Теперь, перейдя в консоли в корневую папку фреймворка (trunk, если вы качали весь репозиторий), осталось запустить build проекта, для чего вводим (у Вас должен стоять ant):
BSiDeUp-Mac-mini:trunk bsideup$ ant build

Можете сходить и сделать себе кофе/поставить сушиться сухари/откинуться на кресле (нужное подчеркнуть), т.к. процесс займёт порядка 5 минут.
(Hint: после 1го билда можно будет запускать только ant compiler, тогда будет собираться только модуль компилятора)

Дождавшись примерно такой надписи:
	main:
	     [echo] ant main target completed on 09/16/2011 11:54:25 AM

	BUILD SUCCESSFUL
	Total time: 5 minutes 33 seconds
	

Радуемся и ликуем, иначе — перечитываем мануал заного и ищем, что пропустили.

Заключение


Теперь Вы с уверенностью можете указывать наш фреймворк как Flex Framework Location в Вашей любимой IDE, тем самым начав использовать модифицированный компилятор, поддерживающий аттрибут new!

В этот раз мы сделали обратно-совместимое изменение, позволяющее компилить другие проекты с использованием MXML без проблем, а в следующей статье, если тема будет интересна, я расскажу, как изменить стандартное поведение MXML, сделав жизнь программисту ещё проще, но теряя обратную совместимость.

Для тех, кому интересен результат, а не процесс, привожу ссылку на уже готовый файл mxmlc.jar, достаточно скачать Flex SDK 4.5 и заменить им файл ${FLEX_SDK_DIR}/lib/mxmlc.jar.

Спасибо за внимание.
+34
1274
38
bsideup 12,3

Комментарии (8)

0
Orion #
если тема будет интересна
Очень интересно, голосую за следующую статью.
0
bsideup #
Спасибо за проявленный интерес) След. статья будет примерно к четвергу, в которой я разберу инициализаторы для свойств, как отключить проверку соответствия и добавить что-то вроде someString=«SomeClass.SOME_CONST + (3 * 5).toString()» без необходимости использовать фигурные скобки и биндинг)
0
flashguy #
Конечно, интересна, пиши ещё!
+1
Aquahawk #
Я просто мечтаю о возможности добавить в строгий режим компилятора строгую проверку на сигнатуры принимаемых и передаваемых функций. Что-то типа Typed callback только мощнее и строже.
0
Aquahawk #
Причём сделать это можно и комментарием, для сохранения обратной совместимости. Только так, чтоб наш компайлер ругался на объявление поля типа Function без такого комментария.
0
bsideup #
идея хороша, но чуть другую область компилятора затрагивает) хотя, ей тоже планировал уделить внимание в цикле статей. Возможно, возьму Вашу хотелку за пример в статье, спасибо за мысль.
0
zEvg #
Что-то не совсем понял, а где искать сорс компилятора?
У меня нет такого пути modules/compiler/src/java/flex2/compiler/mxm в папке с SDK (4.1).
+1
zEvg #
Все, я понял. Надо делать checkout SDK. В поставке вендора нет кода компилятора.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.