Using Hamcrest to Filter ArrayCollections

August 11, 2009 at 12:03 PM | ActionScript | View Comments

Neil Webb's post on filtering an ArrayCollection on multiple property/values reminded me of how I recently solved exactly the same problem using Hamcrest-AS3.

So, you know the story: you've got an ArrayCollection that's full of, say, Laptops, and you want to filter it by, say, price, size and (of course) color.

Because you're a good programmer you always try to do the simplest thing that could possibly work first, and end up with a function something like this:

function laptopFilter(laptop:Laptop):Boolean {
    if (laptop.price > appliedFilters.maxPrice)
        return false;

    if (laptop.size > appliedFilters.maxSize)
        return false;

    if (laptop.color != appliedFilters.preferredColor)
        return false;

    return true;
}

Cool - that defiantly works, and it's pretty simple.

"But wait a second", you're thinking, "what happens when the rules get more complex? That function is quickly going to loose its simplicity!"

Enter, Hamcrest.

Hamcrest is a library of "matchers" which are often used to build unit tests. Here is a quick example of a unit test written with Hamcrest:

[Test]
function testApplePicker():void {
    var apples:Array = ApplePicker.pick("granny smith");
    assertThat(apples.length, greaterThan(4));
    assertThat(apples, everyItem(hasPropery("color", "green")));
}

But, of course, there is no reason that these same matchers shouldn't be used for other things too.

Other things like, say, filtering an ArrayCollection:

var filters:Array = [
    hasProperty("price", greaterThan(1000)),
    hasProperty("color", either("red").or("blue").or("green")),
    hasProperty("size", between(10, 15))
];

var laptopFilter(laptop:Laptop):Boolean {
    return allOf.apply(null, filters).match(laptop);
}

Of course, I've hard-coded the values in this example, but it doesn't take much creativity to imagine how they could be dynamically generated, keeping the code nice and simple while still allowing for complex rules.

So, that's how I filter ArrayCollections :-)

Permalink + Comments

Overriding private methods in ActionScript, the hard way

July 10, 2009 at 10:49 AM | ActionScript | View Comments

Are you working with a proprietary .swc? Does it have lots of bugs? Did the authors, in all their infinite wisdom, decide to hide those bugs in private methods, so you stand no chance of being able to fix them? Well, friend, let me show you how to fix that.

Step 0: Uncompressing the swc/swf

First make a backup copy of the library (just incase you botch something):

 $ cp binary_library.swc binary_library_backup.swc

Then unzip the swc file like you would any other zip file:

 $ unzip binary_library.swc

This will extract, among other things, a library.swf file.

This library.swf will probably be compressed, so you've also got to uncompress it. To do that, I like to use SWFTOOLS:

 $ swfcombine -d library.swf -o uncompressed_library.swf

Step 1: Dumping the ABC

Now that you've got a nice uncompressed swf, it's time to decompile it. To do this, you can use or my patched version of swfdump.

 $ java -jar swfdump.jar -abc library.swf > library.abc

Step 2: Finding the multiname pool entry

Now pull up library.abc in your favorite text editor and search for the method's name under the "MultiName Constant Pool Entries" heading. For example, if the method's name is "priv", you'll be looking for something like this:

6 MultiName Constant Pool Entries
07 01 04 private:priv <-- here
07 02 06 :void
07 02 09 :pub
07 02 01 :A
07 02 10 :Object

Note that the method may be prefixed with a namespace:

6 MultiName Constant Pool Entries
...
07 01 04 com.foo.ClassName:methodName

Technical details: The first of those three bytes refers to the entry kind. The second is a one-based index into the list of namespaces (which are listed under "Namespace Constant Pool Entries") and the third is a one-based index into the list of string constants ("String Constant Pool Entries"). For all the details, see section "4.3 Constant Pool", "4.4.3 Multiname" and "4.1 Primitive data types" (for the definition of u30 – making sure to read the paragraph witch mentions that it uses little-endian byte order) in AVM 2 Machine Overview.

Note: If you're dealing with a large .swf, there may be four or even five bytes:

07 01 B9 03 :methodName

Don't worry – that's ok!

Make note of the bytes (in this case, 07 01 04) which prefix the method, then move on to step 3.

Step 3: Find a similar method

Next thing you've got to do is find a method of the same class which has a pubic (or protected) scope. In this case, we have a method called 'pub':

6 MultiName Constant Pool Entries
07 01 04 private:priv
07 02 06 :void
07 02 09 :pub <-- here

Make note here of the byte (02): that tells the AVM which namespace the method belongs to.

Step 4: Hex edit the uncompressed library

Now, pull up your favorite hex editor and open up the uncompressed_library.swf, which you created in step 0. Now, remember those bytes which prefix the method name? Search for them (and make sure you only find one instance of them – if there are multiple instances of them, expand the search to take into account the next few bytes (so, in this case, 07 01 04 07 02 06). Now, once you've found them, simply replace the second byte (in this case 01) with the second byte of the similar method:

07 01 04

is replaced with:

07 02 04

Step 5: Checking that you got it right

To check and make sure that you got it right, run the swfutil again, then check the output:

$ java -jar swfdump.jar -abc uncompressed_library.swc

You should see your change reflected in the MultiName constant pool entries:

6 MultiName Constant Pool Entries
07 02 04 :priv
07 02 06 :void

Note that the method is no longer prefixed with 'private' (or whatever the original namespace was).

Now, if the utility crashes, or something else doesn't work, go back and try again – you'll get it eventually.

Step 6: Putting it all back together

You're almost done! All that's left to do is put everything back into the .swc.

First, copy uncompressed_library.swf over top of library.swf:

$ cp library_uncompressed.swf library.swf

Or, optionally, you can re-compress it:

$ swfcombine -z -d library_uncompressed.swf -o library.swf

Then put library.swf back into the swc:

$ zip binary_library.swc library.swf

Finally try linking against it to see if everything worked!

See also:

Permalink + Comments