Powerful filtering on collections in Flex

People search for things all the time. It is in our genes and we use tools to achieve our goals. On the internet a lot of us use Google to find what we need. So most of us are familiar with Google search techniques. Now some of these search techniques are put into a very powerful filter function for Flex.

The features supported

This filter has an ignore list too. If you add words to it, you can make it even more Google search like. For example Google ignores words like “a” or “the” to help their users in their quest for information.

Of course you do not have to use features like the ignore list, the synonyms and the case sensitive option. If you do not use these features you still have a very powerful filter that’s easy to implement. A nice feature to extend this filter with in your application would be to allow the user to select the columns to search in. This column selection is not in the demo application, but my guess is you are perfectly capable to do it yourself.

The source

The only code shown is the code of the demo application, but you can download the full source. The other code is not put on the page, because it are too many files to put on a webpage. Do not let this scare you, because it is not that much or that complicated.

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
               xmlns:s="library://ns.adobe.com/flex/spark"
               xmlns:mx="library://ns.adobe.com/flex/mx"
			   creationComplete="creationCompleted(event)"
			   width="400" height="265">
	<fx:Script>
		<![CDATA[
		import mx.collections.ArrayCollection;
		import mx.events.FlexEvent;
		import nl.vanhulzenonline.collections.filter.Evaluator;
		import nl.vanhulzenonline.demo.Album;
		import spark.components.gridClasses.GridColumn;
		
		private var _collection:ArrayCollection;
		private var _evaluator:Evaluator = new Evaluator();
		
		private function creationCompleted(event:FlexEvent):void
		{
			_collection = Album.collection;
			_collection.filterFunction = filterCollection;
			
			_evaluator.synonyms["four"] = new ArrayCollection(["4"]);
			
			grid.dataProvider = _collection;
		}
		
		private function filterChanged(event:Event):void
		{
			update();
		}
		
		private function update():void
		{
			_evaluator.prepare(filter.text);
			_collection.refresh();
			
			formula.text = (_evaluator.tree) ? _evaluator.tree.toString() : "";
		}
		
		private function filterCollection(data:Object):Boolean
		{
			var labels:ArrayCollection = new ArrayCollection();
			for (var i:int; i < grid.columns.length; i++)
			{
				labels.addItem(
				  (grid.columns.getItemAt(i) as GridColumn).itemToLabel(data));
			}	
			return _evaluator.evaluate(labels);
		}
		
		private function caseSensitiveFilterChanged(event:FlexEvent):void
		{
			_evaluator.caseSensitive = caseSensitiveFilter.selected;
			update();
		}
		
		]]>
	</fx:Script>
	
	<s:VGroup left="5" right="5" bottom="5" top="5" >
		<s:TextInput id="filter" width="100%" change="filterChanged(event)" />
		<s:CheckBox id="caseSensitiveFilter" label="filter case sensitive" 
			valueCommit="caseSensitiveFilterChanged(event)" />
		<s:DataGrid id="grid" width="100%"  height="100%" >
		</s:DataGrid>
		<s:Label id="formula" />
	</s:VGroup>
</s:Application>

Fast searching in a ByteArray

For some reason there is no method to search for a specific text in a ByteArray. At least i could not find one. I tried a very straightforward search by simply scanning from every position. Although it works, it is very slow with bigger ByteArrays.

A quick look on the internet resulted in an algorithm that suited my needs: the Boyer-Moore-Horspool algorithm. Unfortunately there was no ActionScript alternative available for it yet. So i decided to port it myself and with great result, because it greatly improved the performance of the searches for my use case.

package nl.vanhulzenonline.utils
{
	import flash.utils.ByteArray;

	public class ByteArrayUtils
	{
		public static function getIndexOf(text:String, data:ByteArray, start:int = 0, end:int = -1):int
		{
			var pattern:ByteArray = new ByteArray();
			pattern.writeUTFBytes(text);
			pattern.position = 0;

			if (end == -1)
				end = data.length - 1;

			var i:int;
			var badCharSkip:Array = new Array();

			// initialize the table to default value
			// when a character is encountered that does not occur
			// in the pattern, we can safely skip ahead for the whole
			// length of the pattern.
			for (i = 0; i <= 255; i++)
				badCharSkip[i] = pattern.length;

			// then populate it with the analysis of the pattern
			var endOfPattern:int = pattern.length - 1;
			for (i = 0; i < endOfPattern; i = i + 1)
				badCharSkip[pattern.readUnsignedByte()] = endOfPattern - i;

			// do the matching

			// search the data, while the pattern can still be within it.
			var dataPart:int;
			var endOfData:int = end;
			var dataPosition:int = start;
			while (endOfData >= endOfPattern)
			{
				// scan from the end of the pattern
				i = endOfPattern;
				while(true)
				{
					data.position = dataPosition + i;
					pattern.position = i;
					if (data.readUnsignedByte() == pattern.readUnsignedByte())
					{
						// if the first byte matches, we've found it.
						if (i == 0)
							return dataPosition;
						i--;
					}
					else
						break;
				}

				// otherwise, we need to skip some bytes and start again.
				// note that here we are getting the skip value based on
				// the last byte of pattern, no matter where we didn't
				// match. so if pattern is: "abcd" then we are skipping
				// based on 'd' and that value will be 4, and for "abcdd"
				// we again skip on 'd' but the value will be only 1.
				// the alternative of pretending that the mismatched
				// character was the last character is slower in the normal
 				// case (eg. finding "abcd" in "...azcd..." gives 4 by
				// using 'd' but only 4-2==2 using 'z'.
				data.position = dataPosition + endOfPattern;
				dataPart = data.readUnsignedByte();
				endOfData -= badCharSkip[dataPart];
				dataPosition += badCharSkip[dataPart];
			}
			return -1;
		}
	}
}

Automate the uninstall of an AIR application on Windows

It is fairly easy to install an Adobe AIR application from the command line on Windows, but an uninstall is a totally different story. After searching the internet quite a bit for a solution, there did not seem to be easy way to uninstall an Adobe AIR application. However it became more clear how an Adobe AIR application is installed. It was time to check the registry. This brought a smile to my face, because there it was: the command to uninstall the application.

I have built a small program in c-sharp to get the uninstall command from the registry. The input is the path to the installed application. Next is a sample of how to uninstall an application. This sample removes an installed Adobe example application called EchoExample. It was installed in C:\Program Files\EchoExample.

GetAirApplicationUninstallString "C:\Program Files\EchoExample" > uninstall.bat
call uninstall.bat

You can get a compiled version of GetAirApplicationUninstallString here and it’s source is shown down here.

using System;
using System.Xml;

using Microsoft.Win32;

namespace GetAirApplicationUninstallString
{
    class Program
    {
        static public string getElementValue(string xmlFileName, string elementName)
        {
            XmlTextReader reader = new XmlTextReader(xmlFileName);
            while (reader.Read())
            {
                if (reader.Name.Equals(elementName))
                {
                    reader.Read();
                    return reader.Value;
                }
            }
            return "";
        }

        static void Main(string[] args)
        {
            String applicationPath = args[0];

            String applicationId = getElementValue(applicationPath + "\\META-INF\\AIR\\application.xml", "id");
            String publisherId = getElementValue(applicationPath + "\\META-INF\\AIR\\application.xml", "publisherID");
            if (!publisherId.Equals(""))
                applicationId += "." + publisherId;

            RegistryKey key = Registry.LocalMachine.CreateSubKey(
                "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\" + applicationId);

            Console.Out.WriteLine(key.GetValue("UninstallString"));
        }
    }
}