-
Notifications
You must be signed in to change notification settings - Fork 0
Writing custom Patchers
= Introduction =
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 in the JVM by a custom classloader gwt-test-utils provides to launch JUnit 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 handle 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 [http://code.google.com/intl/en-EN/webtoolkit/doc/latest/DevGuideCodingBasicsJSNI.html 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 :
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.