MXML compiler. Part 2. Non-string parameter initializers

    Hello, Habr!

    In a previous article, I conducted some educational program on the internals of the Flex compiler (in terms of MXML) and talked about how to get rid of the problems of working with classes in MXML that require constructor parameters. Now we will analyze the other side of the issue - setting parameters to custom values ​​(for example, constants) without using the Binding mechanism (although it is very similar to it).


    introduction

    I want to apologize in advance for the fact that this article came out later than I said - literally the very next day after the article I was hired, and there was simply no time :) Now everything has been resolved, and I am ready to continue the series of articles.

    I think everyone who worked with Flex in one way or another came up with something like this:

    If you look at the code that mxmlc generates, it will look something like this:
    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;
        }
    

    Isn't there a lot of code for such a trivial task of specifying a property as a constant, which by definition does not change?

    Let's do it!


    Our goal is to make a backward compatible modification that would support autocomplete in civilian IDEs, plus would be visually familiar to Flex programmers. Given all of the above requirements, I decided that the solution would look like this:

    The idea to use the dollar symbol came from ANT-scripts, it is convenient and clear, and it is already familiar to a good half of flashers.

    Frankly, I started the process by searching for all classes in the java.flex2.compiler.mxml.lang package (why in it? See the first article in the loop;)) for the entry of the string "@ {", because it is with her that the so-called two-way bindings begin, and our implementation could be based on them. And I got lucky! The parseBindingExpression method was immediately detected in java.flex2.compiler.mxml.lang.TextParser .

    Objects of type BindingExpression are used to denote the Binding expression , therefore, by analogy, we will create an ExactValueExpression class next to it (in the package flex2.compiler.mxml.rep):
    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;
    	}
    }
    

    We will use it in our own method of the java.flex2.compiler.mxml.lang.TextParser class , which I decided to call 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 );
    	}
    

    Which we will call BEFORE calling 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);
                    }
                }
    


    Now our design is correctly parsed, it remains only to process it. We go to the java.flex2.compiler.mxml.rep.init.ValueInitializer method formatExpr that we already know from the previous article . Add 3 lines to it:
    if(value instanceof ExactValueExpression)
    {
    	return ((ExactValueExpression) value).getValueExpression();
    }
    

    We assemble the project with the ant compiler command , wait for the BUILD SUCCESSFUL (if you did not get such a result, check all the steps from the beginning).

    Result


    Let's change our original example by adding a dollar symbol:

    If you did everything correctly, the result will be something like this:
    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;
    }
    


    Conclusion


    I carried out this modification today at the end of the lunch break (about 20-30 minutes), of which about 3/4 of the time was spent on determining where to put something . Do not be afraid to delve into open source projects, try to modify them and do better, especially if you have to work with them.

    Thank you all for your attention, I will be glad to hear what you want to write about in the next articles of the cycle!

    Also popular now: