Clone Wars! Which side are you on?

So I was watching a few people talk about cloning in actionscript today. And soon I’ll have to be doing cloning on my own as well. So I thought I’d present the options and my own solution.
Option 1: Use ObjectUtil.copy
The ObjectUtil.copy isn’t too bad it does ByteArray serialization/deserialization. So you get some nice features like deep copy. However (and here’s the bullet to byte) you have to use either RemoteObject metadata or registerClass actionscript. You can read more over here. So if you’re using a RemoteObject metadata or registerClass then this solution is for you! If not true believer then keep on reading.
Option 2a: Create/Implement a clone method
So for this one you would first create an interface and then implement it.
public interface IClone{
    function clone():IClone;
}
public class MyCloneClass implements IClone{
public var cloneProperty1:String;
    public var cloneProperty2:String;
    public function clone():IClone{
          var myClone:MyCloneClass = new MyCloneClass();
          myClone.cloneProperty1 = this.cloneProperty1;
          myClone.cloneProperty2 = this.cloneProperty2;
    }
}

So for this one you might think that it’s not too bad or overly complicated. You’re right not complicated at all. However (and here’s the bullet to byte) this can get unwieldy with large property sets or a large Class collection, you have to implement the interface/method and then provide the needed assistance to pull this out. If you have time for this great! It’s simple to do, simple to read, simple to maintain, works the hell out of KISS (keep it simple stupid!).


Option 2b: Create a generic clone method
So option 2a is nice. But can become time intensive. So here is one variation that is great but not the best OO
        public function clone( cloneObj:Object , instance:Object = null ):Object {
            if( instance != null ) {
                var c:Class = cloneObj['constructor'] as Class;
                instance = new c();
            }
            var classInfo:XML = describeType(cloneObj);
            // List the object's variables, their values, and their types.
            for each ( var v:XML in classInfo..variable ) {
                instance[v.@name] = cloneObj[v.@name];
            }

            // List accessors as properties.
            for each ( var a:XML in classInfo..accessor ) {
                // Do not user the property if it can't be read or written
                if( a.@access == 'readwrite' ) {
                    instance[ a.@name ] = cloneObj[a.@name];
                }

            }

            return instance;
        }

I like this option quite a bit. It requires you to create the new instance and pass it in or you can let the function try to create the new object but the class will require a constructor that doesn’t need any parameters passed in. So this function uses describeType to get an understanding of the class information of the passed in object and to map it’s variables and accessors. Notice that the accessors require a ‘readwrite’ access, since we need to read the value from the old object and assign the value to the clone object.


Option 2c: Create a clone method that’s a little from column A and a little from column B
public class ValueObject {
               public function clone( deep:Boolean = false , instance:ValueObject = null ):ValueObject
        {
            if( instance == null )
            {
                var c:Class = this['constructor'] as Class;
                instance = new c() as ValueObject;
            }
            // get class info from instanstiated object (aka the 'this' pointer)
            var classInfo:XML = describeType(this);
            var propertyName:String;
            var propertyValue:Object;
            // List the object's variables, their values, and their types.
            for each ( var v:XML in classInfo..variable )
            {
                propertyName = v.@name;
                propertyValue = this[propertyName];
                checkObjectTypeAndClone(propertyName , propertyValue , deep , instance);
            }

            // List accessors as properties.
            for each ( var a:XML in classInfo..accessor )
            {
                // Do not user the property if it can't be read or written
                if( a.@access == 'readwrite' )
                {
                    propertyName = a.@name;
                    propertyValue = this[propertyName];
                    checkObjectTypeAndClone(propertyName , propertyValue , deep , instance);
                }

            }

            return instance;
        }
        protected function checkObjectTypeAndClone( propertyName:String , propertyValue:Object , deep:Boolean , instance:Object ):void
        {
            if( deep && propertyValue is ValueObject )
            {
                instance[propertyName] = ValueObject(propertyValue).clone(deep);
            }
            else if( propertyValue is Array )
            {
                instance[propertyName] = cloneArray(propertyValue as Array , deep);
            }
            else if( propertyValue is ArrayCollection )
            {
                instance[propertyName] = new ArrayCollection(cloneArray(ArrayCollection(propertyValue).source , deep));
            }
            else
            {
                instance[propertyName] = propertyValue;
            }
        }
        protected function cloneArray( val:Array , deep:Boolean = false ):Array
        {
            var clonedObj : Array = new Array;
            for( var i:int = 0 ; i < val.length ; i++ )
            {
                if( deep && val[i] is ValueObject )
                {
                    clonedObj[i] = ValueObject(val[i]).clone(true);
                }
                else if( val[i] is Array )
                {
                    clonedObj[i] = cloneArray(val[i] , deep);
                }
                else if( val[i] is ArrayCollection )
                {
                    clonedObj[i] = new ArrayCollection(cloneArray(ArrayCollection(val[i]).source , deep));
                }
                else
                {
                    clonedObj[i] = val[i];
                }
            }
            return clonedObj;
        }
}


This option requires that any object you want cloned extends ValueObject. Also if you notice this function takes into consideration a need for a deep copy of only other ValueObjects and Array/ArrayCollection. Now if you do a deep copy all constructors will need to allow for no parameter pass in. I like this one the most, as it takes from option A and option B and makes it easy but extending the ValueObject, although there is the issue of basic Collection, such as if you wanted to clone an XMLList or clone a dynamic hash array, you would have to build that functionality and testing in. This one trades off not having to write a specific clone implementation for each method with having to design to get to underlying object that might be contained inside a property.

|

Pageable Row Set???

So my coworker Justin has been telling me that I should be writing a blog about some of the stuff I’ve been working on with Flex and Java. So here’s my start....

I’ve been working with decently large datasets, about 10k to 20k rows of data. The data has many attributes, so the size of it is quite large, 10M or so.

Of course I started with a nice gzip on the server, so that compressed the data down which increases the download of the dataset, but of course this is a stop gap measure because as the size of the data grows the difficulty of dealing with it grows.

I’ve looked at java side RowSet and using BlazeDS PageableRowSetCache, with a homegrown actionscript paged array collection.

I didn’t find much information to do this, so I thought I would write up what I did.

First, I did the basic java remote method.



RowSet rowSet = new WebRowSetImpl();
rowSet.setReadOnly(true);
rowSet.setDataSourceName("java:comp/env/jdbc/DATASOURCE_CONNECTION");
rowSet.setCommand("SQL STATEMENT");
rowSet.execute();

With this row set we can now call the PageableRowSetCache.cacheRowSet.

PageableRowSet pRowSet = PageableRowSetCache.cacheRowSet(rowSet, 15);

We now pass the PageableRowSet back to the Flex side. The nice thing is that the PageableRowSet gets converted into an mx.util.ObjectProxy that has the following properties that I use.
serverInfo:Object which contains
    id:String // unique
    totalCount:int
    initialData:Array
    serviceName:String

The initialData holds the first page of data and you should use the length to identify the pageSize to fetch.
The totalCount holds the complete row count of the SQL STATEMENT that was excecuted.
The serviceName holds the destination name for requesting pending data. It has a default value of PageableRowSetCache, you have to setup the JavaAdapter in the remoting-config.xml file i.e.
<destination id="PageableRowSetCache">
        <properties>
            <scope>application</scope>
            <source>flex.messaging.services.remoting.PageableRowSetCache</source>
        </properties>
</destination>


The flex.messaging.services.remoting.PageableRowSetCache has a method called 00001: getRecords(id:String, start:int, numOfRecords:int)   which is used to retrieve other data from the Row Set. The id is the unique id in the serverInfo object.

So at this point we’ve set up everything without writing much Flex code. So now comes the bigger piece of it.

public class PageableCollection extends ArrayCollection {
    public static var pendingObject:Object = "Pending";
    protected var pagedFetched:Object = new Object();
    protected var pagedObject:Object;
    protected var pageService:RemoteObject;
    protected var pageSize:Number;
    public function PageableCollection(obj:Object) {
        pagedObject = obj;
        var array:Array = new Array(pagedObject.totalCount + 1);
        var i:int;
        for (i = 0; i < pagedObject.initialData.length; i++) {
            array[i] = (convert(pagedObject.initialData[i] as Array));
        }
        pageSize = i;
        for (; i < pagedObject.totalCount; i++) {
            array[i] = pendingObject;
        }
        super(array);
        pageService = new RemoteObject(pagedObject.serviceName);
        pageService.addEventListener(ResultEvent.RESULT, handleResult, false, 1, true);
    }
    override public function get length():int {
        return pagedObject.totalCount + 1;
    }

    override public function getItemAt(index:int, prefetch:int = 0):Object {
        var obj:Object = super.getItemAt(index, prefetch);
        if (obj != pendingObject) {
            return obj;
        }
        var page:int = Math.floor(index / pageSize);
        if (pagedFetched[page] != null) {
            return obj;
        }
        pagedFetched[page] = true;
        var token:Object = pageService.getRecords(pagedObject.id, page * pageSize, pageSize);
        token.index = page * pageSize;
        return obj;
    }
    protected function handleResult(event:ResultEvent):void {
        for (var i:int = 0; i < event.result.Page.length; i++) {
            this.setItemAt(convert(event.result.Page[i]), i + event.token.index);
        }
    }
    public var  convert:Function = function(a:Object):Object{
        return a;
    };
    }
}

A few things to remember about this are
  • You need a java sticky session on the server side.
  • Sorting and filtering can be problematic, (I’m hoping to have a post to address this issue)
  • You need to manage the PageableRowSetCache, such as releasing it when you’re done

The one thing I really like about this implementation is how it allows the ArrayCollection to deal with keeping the data and this class to deal with acquiring the pending data, as well as being able to replace an ArrayCollection with a PageableCollection.


|