Skip to content

Writing custom Patchers

Gael Lazzari edited this page Aug 24, 2012 · 2 revisions

The main challenge for gwt-test-utils has been to enable the instanciation of all GWT client side widgets in a standalone JVM and to simulate their behaviours in Java rather than in JavaScript. This is done by modifying GWT classes as they are loaded by a custom classloader gwt-test-utils provides to launch your tests.

But don't worry, even if you are interested in writing your own custom bytecode modifications, you will never have to deal with classloading stuff : gwt-test-utils provide a very simple "Patcher" API, which handles all loading and bytecode modification issues behind the scene.

The Patcher API

Providing a "patch" for an existing class is done in two steps :

  • writing some PatchClass for this class
  • registering thoses patch classes to gwt-test-utils

Writing a custom patcher

The patching mechanism relies on 2 annotations and some convention. Let's introduce them with an example taken from the UiObject class from the GWT API :

public abstract class UIObject {

  private native void replaceNode(Element node, Element newNode) /*-{
    var p = node.parentNode;
    if (!p) {
      return;
    }
    p.insertBefore(newNode, node);
    p.removeChild(node);
  }-*/;
}

The private method replaceNode implementation is written in JSNI, so it can't be executed in a standard JVM.

That's why we need to replace it by a Java implementation. This is done with a PatchClass:

@PatchClass(UIObject.class)
class UIObjectPatcher {

  @PatchMethod
  static void replaceNode(UIObject uiObject, Element node, Element newNode) {
    Node parent = node.getParentNode();
  
    if (parent != null) {
      parent.insertBefore(newNode, node);
      parent.removeChild(node);
    }
  }
}
  • Mark your class with @PatchClass to tell gwt-test-utils on which classes it should be applied.
  • Mark your substitution method with @PatchMethod. A patch method must follow several conventions :
  1. Static method only.
  2. Same name as the method to substitute.
  3. Same return type.
  4. If the method to substitute was static, the patcher method must declare exactly the same parameters in its signature. Otherwise, it must take a reference to the initial caller (or any compatible type) as its first argument, followed by the same parameters as the method to substitute.

Registering custom patchers

In your META-INF/gwt-test-utils.properties file (which must be available in your test classpath), simply add this line :

my.patcher.package = scan-package

As you may have guessed, my.patcher.package must by the package of your @PatchClass annotated classes.
Before loading any class, gwt-test-utils will scan every file and directory within my/patcher/package to find classes with the @PatchClass annotation.

You could also have declared my.patcher as the root package to scan, but you should consider performance concerns when scanning a large number of files.

Note that your patch classes and patch methods can either be public or private. The framework will make them public at runtime, regardless of their declared visibility.

Patching private classes

You may want to patch a private class. In this case, the annotation parameter @PatchClass(MyPrivateClass.class) won't fit since you can't reference MyPrivateClass.

Well, just use it this way :

@PatchClass(target = "my.package.MyPrivateClass")
class MyPrivateClassPatcher {
  ...
}

The target attribute of @PatchClass simply takes the full name of the class you want the patcher to be applied on instead of the Class type itself.

Overriding existing patchers

In some case, you might want to override an already provided patch method. This example shows how to override the UiObject.replaceNode() java implementation provided by gwt-test-utils :

@PatchClass(UIObject.class)
class MyUiObjectOverridenPatcher {

  @PatchMethod(override = true)
  static void replaceNode(UIObject uiObject, Element node, Element newNode) {
    // new java code here
  }
}

Simply add an override = true attribute to your new implementation and the trick is done ! Just remember there can only be one override PatchMethod in your patch classes.