« Persepolis | Main | Slow Motion Moment »

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

Comments

Proxy is weird in many ways, there is also the case where method calls qualified by namespaces end up in getProperty, instead of callProperty:

myProxy.myNamespace::myMethod()

Which really makes no sense. Proxy is nice in theory, but lacking in practice. I would really like to see something that didn't require subclassing, for example allowing you to declare a method called callProperty in the flash_proxy namespace on any class and having it work as the old __resolve.

I've not noticed any strange behavior with using namespaces. I updated the example usage to include the use of a namespace and everything looks to be working. Still no luck with triggering an isAttribute() call though.
I answered the issue in an email but thought I'd leave a comment here on your blog for those who might happen upon the same issue. "isAttribute" isn't for overriding, it's to use with the getProperty method. e.g.
override flash_proxy function callProperty(name:*, ... rest):* {
    if (isAttribute(name)) {
        trace("callProperty", "@" + name, rest);
    else
        trace("callProperty", name, rest);
    return null;
}
That allows you to handle properties either way in the one function.
Thanks for the insight Jacob. I've updated MyProxy to include using isAttribute() and also exercise it in the sample usage.