Thursday 21 July 2011

Quick Tip: Passing Varargs Parameters

Occasionally the need arises to pass the varargs parameters of a function directly onto another function that accepts varargs, such as if you are writing a wrapper or using a Composite or Decorator pattern to add complimentary behaviour. Suppose we have the following interface:

package
{
 public interface IMyInterface
 {
  function myFunction( singular: String, ... varargs ): void;
 }
}

If we're implementing a basic Decorator-style pattern to extend some functionality, we're going to want to pass the parameters on to the child unaffected. If we tried the following:

package
{
 public class NewFeature implements IMyInterface
 {
  private var _child: IMyInterface;

  public function NewFeature( child: IMyInterface )
  {
   _child = child;
  }

  public function myFunction( singular: String, ... varargs ): void
  {
   _child.myFunction( singular, varargs );
  }
 }
}

Then the varargs will get passed to the child function's varargs parameter as a single array, rather than the unrolled varargs. That is, assuming we had the following:

package
{
 public class CoreFeature implements IMyInterface
 {
  public function myFunction( singular: String, ... varargs ): void
  {
   trace( "Singular: " + singular );
   trace( "Varargs: " + varargs );
  }
 }
}

and then made a call as per:

var feature: IMyInterface = new NewFeature( new CoreFeature(  ) );

feature.myFunction( "foo", "bar", "qwerty", "asdf" );

Then the debug console would contain the following output:

Singular: foo
Varargs: [ [ bar, qwerty, asdf ] ]

That is, when passing the varargs variable to a function call, it is treated as an array. Thus, when it is passed on to a varargs parameter, it becomes the first element of the subsequent varargs array.

The fix here is to use some basic runtime reflection to call the function, rather than invoking it directly. By using the Function.apply() method, we're able to pass an array of parameters into the function call. AVM2 will correctly match up the explicit parameters, and then group the rest into the varargs variable. For example:

package
{
 public class NewFeature
 {
  private var _child: IMyInterface;

  public function myFunction( singular: String, ... varargs ): void
  {
   varargs.unshift( singular );
   _child.myFunction.apply( _child, varargs );
  }
 }
}

In this example, we need to stick singular at the start of the array, so that it is correctly applied to the child function.

Calling the function as per the example above, would now result in the following debug console output:

Singular: foo
Varargs: [ bar, qwerty, asdf ]

Sunday 10 July 2011

Remote Debugging in FlashBuilder

Disclaimer: There may be a better way to achieve this goal, but this trick has served me well for many years.

Occasionally the need to debug a Flex application that has not been launched from FlashBuilder arises: the application may be present in a UAT environment or it might need to be tested outside of the security-sandbox-free-zone.. In any case, you may not be able to launch it by simply clicking the "Debug" button on FlashBuilder's toolbar.

A workaround to this problem involves creating another debug profile for the application, by either selecting the "Run -> Debug Configurations..." menu item, or clicking the down arrow to the right of the "Debug" toolbar button and selecting "Debug Configurations..." In this screen, you should right-click on the configuration of the application of note, and select "Duplicate." The duplicated item will appear with a name like, "SampleApplication (1)." Select this debug profile in the tree, and then change the "URL or path to launch" configuration as follows:

  1. Untick the "Use default" checkbox.
  2. Enter "about:blank" as the URL.
Save your configuration by clicking the "Close" button, or launch it by clicking the "Debug" button.

The idea here is that when you launch this new debug profile, the browser associated with FlashBuilder will not open a page containing the Flex application, and thus a debug connection will not be established. However, FlashBuilder will still open up a debug listening service that will wait a short time before terminating. During this time, it is possible to manually load the application to be tested. As the application loads, it should connect to the debug session waiting within FlashBuilder, permitting the usual debug operation.