February 25, 2008

Puzzling Proxy Problem: Solved

The short version is what AS3 operator triggers a call to the isAttribute() method of a Proxy? The answer, none. isAttribute() is a utility function provided by Proxy that can be called to determine if the name passed to a function was specified as an attribute. A couple of Proxy's methods can't be called in an attribute context, as noted below. I've created a sample Proxy class called MyProxy to help show what's going on:

package com.neophi.test {
    import flash.utils.Proxy;
    import flash.utils.flash_proxy;

    public dynamic class MyProxy extends Proxy {
        override flash_proxy function callProperty(name:*, ... rest):* {
            trace("callProperty", name, rest);
            flash_proxy::isAttribute(name);
            return null;
        }

        override flash_proxy function deleteProperty(name:*):Boolean {
            trace("deleteProperty", name);
            flash_proxy::isAttribute(name);
            return false;
        }

        override flash_proxy function getDescendants(name:*):* {
            trace("getDescendants", name);
            flash_proxy::isAttribute(name);
            return null;
        }

        override flash_proxy function getProperty(name:*):* {
            trace("getProperty", name);
            flash_proxy::isAttribute(name);
            return null;
        }

        override flash_proxy function hasProperty(name:*):Boolean {
            trace("hasProperty", name);
            flash_proxy::isAttribute(name);
            return false;
        }

        // Don't override isAttribute(), it is a utility function
        // used by methods that specify a name:* parameter
        // to determine if the name argument was specified as an
        // attribute. It doesn't look like there is any other
        // way to determine if a name was specified as an
        // attribute besides calling Proxy's isAttribute()
        // implementation. 
        override flash_proxy function isAttribute(name:*):Boolean {
            var result:Boolean = super.flash_proxy::isAttribute(name);
            trace("isAttribute", name, result);
            return result;
        }

        override flash_proxy function nextName(index:int):String {
            trace("nextName", index);
            return null;
        }

        override flash_proxy function nextNameIndex(index:int):int {
            trace("nextNameIndex", index);
            return (1 - index);
        }

        override flash_proxy function nextValue(index:int):* {
            trace("nextValue", index);
            return null;
        }

        override flash_proxy function setProperty(name:*, value:*):void {
            trace("setProperty", name, value);
            flash_proxy::isAttribute(name);
        }
    }
}

I then exercise all of the methods (with and without namespaces) on MyProxy with this code:

private function go():void
{
    namespace myNamespace = "com.neophi.test";
    var myProxy:MyProxy = new MyProxy();

    // callProperty()
    myProxy.foo();
    // Compiler error: 1041: Attributes are not callable.
    // myProxy.@foo();
    myProxy.myNamespace::foo();
    // Compiler error: 1041: Attributes are not callable.
    // myProxy.@myNamespace::foo();

    // deleteProperty(): uses isAttribute()
    delete myProxy.foo;
    delete myProxy.@foo;
    delete myProxy.myNamespace::foo;
    delete myProxy.@myNamespace::foo;

    // getDescendents(): uses isAttribute()
    myProxy..foo;
    myProxy..@foo;
    myProxy..myNamespace::foo;
    myProxy..@myNamespace::foo;

    // getProperty(): uses isAttribute()
    myProxy.foo;
    myProxy.@foo;
    myProxy.myNamespace::foo;
    myProxy.@myNamespace::foo;

    // hasProperty()
    "foo" in myProxy;
    // Compiler error: 1084: Syntax error: expecting identifier before foo.
    // @"foo" in myProxy;
    new QName(myNamespace, "foo") in myProxy;
    // Compiler error: 1084: Syntax error: expecting identifier before new.
    // @new QName(myNamespace, "foo") in myProxy;

    // nextName(): uses nextNameIndex() 
    for (var string:String in myProxy) {
        trace(string);
    }

    // nextValue(): uses nextNameIndex() 
    for each (var object:Object in myProxy) {
        trace(object);
    }

    // setProperty(): uses isAttribute()
    myProxy.foo = "bar";
    myProxy.@foo = "bar";
    myProxy.myNamespace::foo = "bar";
    myProxy.@myNamespace::foo = "bar";
}

The output of running is below (whitespace added to match up with blocks above):

callProperty foo
isAttribute foo false
callProperty com.neophi.test::foo
isAttribute com.neophi.test::foo false

deleteProperty foo
isAttribute foo false
deleteProperty foo
isAttribute foo true
deleteProperty com.neophi.test::foo
isAttribute com.neophi.test::foo false
deleteProperty com.neophi.test::foo
isAttribute com.neophi.test::foo true

getDescendants foo
isAttribute foo false
getDescendants foo
isAttribute foo true
getDescendants com.neophi.test::foo
isAttribute com.neophi.test::foo false
getDescendants com.neophi.test::foo
isAttribute com.neophi.test::foo true

getProperty foo
isAttribute foo false
getProperty foo
isAttribute foo true
getProperty com.neophi.test::foo
isAttribute com.neophi.test::foo false
getProperty com.neophi.test::foo
isAttribute com.neophi.test::foo true

hasProperty foo
isAttribute foo false
hasProperty com.neophi.test::foo
isAttribute com.neophi.test::foo false

nextNameIndex 0
nextName 1
null
nextNameIndex 1

nextNameIndex 0
nextValue 1
null
nextNameIndex 1

setProperty foo bar
isAttribute foo false
setProperty foo bar
isAttribute foo true
setProperty com.neophi.test::foo bar
isAttribute com.neophi.test::foo false
setProperty com.neophi.test::foo bar
isAttribute com.neophi.test::foo true

That then is a complete run down of all the Proxy methods and how to use them. I'd like to thank Jacob Wright for the pointer on how isAttribute() is used.

Tags: as3 flex proxy