Jackpot Rule Language
Jackpot has a mini-language for defining pattern matching and
replacement its Java source model. This language allows
developers to create many types of Jackpot operators and
transformers without learning its
API.
It
is not intended to be a complete language or replacement for the
underlying API, however, but is focused on simplifying common
cases. If you have ideas on improving the rule mini-language or
Jackpot in general, please
join
the project and discuss them with us.
Rule Basics
A rule has two required parts: a pattern and a replacement
action. These are separated by the
=>
(pronounced "such that") token, as shown:
<pattern> => <replacement> ;
A pattern is a Java expression or statement that Jackpot should
match. The replacement action is normally (but not always) a
similar pattern to replace the found pattern. This is perhaps the
simplest rule (but not very useful):
false => true; // replace all false constants with true
Here are slightly more useful rules from Jackpot's Cleanup command;
since "this" in Java can never be equal to null, such tests can be
replaced by the equivalent boolean constant:
this == null => false;
this != null => true;
and this is a deprecated variable cleanup rule:
java.awt.Frame.CROSSHAIR_CURSOR=>java.awt.Cursor.CROSSHAIR_CURSOR;
Meta-Variables
Most rules are more complex, however, because they need to be applied to
many different nodes with shared characteristics. These rules use
meta-variables to provide
wildcard-like matching to the source model's nodes.
Meta-variables are regular Java identifiers preceeded by a '$'
character, such as
$a
or
$anotherMetaVariable
(even
$1
and
$_
are valid, but may be hard to read if you don't use Perl or AWK
much). Here are some rules that use meta-variables:
$a && false => false; // unnecessary test
!($a <= $b) => $a > $b; // overly complex boolean expression
do $a; while(false) => $a; // eliminate unnecessary do-while loop
Since Jackpot works on source tree nodes instead of source text, rules
can be applied to parts of an expression. This allows them to
combine to automatically make more powerful rules. For example,
if we apply the above rules to:
do {
someMethod();
} while (isAnotherMethod() && this == null);
It will be turned into:
do {
someMethod();
} while (isAnotherMethod() && false); // this == null => false;
...
do {
someMethod();
} while (false); // $a && false => false;
...
{
someMethod(); // do $a; while(false) => $a;
}
while Jackpot then "de-blocks" to:
someMethod();
Guard Expressions
One thing not obvious from the above section is how Jackpot matches
some nodes but not others. For example, the "
$a && false => false;"
rule should only match boolean expressions, but
$a
is not typed to be boolean. The answer is that the type of
$a
is inferred by the expression; in other words, since only boolean nodes
are valid in that expression, those are the only nodes which are
tested.
The issue arises with expressions which can accept multiple types, but
you only want to match specific ones. For example, suppose you
want to replace use of the deprecated
java.awt.Component.enable()
method with
java.awt.Component.setEnabled(true):
java.awt.Component.enable() => java.awt.Component.setEnabled(true);
This doesn't work since
Component
is an abstract class; we want it to fix all
Component subclass
references. So we use a meta-variable (remember, there is nothing
special about meta-variable names. I picked
$C only
because 'C' is the first character of
Component):
$C.enable() => $C.setEnabled(true);
The problem now is that my project has non-
Component classes which have an
enable() method, since it
is a common name. What we want is a rule that matches only those
expressions where
enable()
is invoked on a
Component
instance.
Rules optionally constrain possible matches using a
guard expression, which is a boolean
expression that accepts or rejects possible matches. A guard
expression is separated from the rule body by the
::
(pronounced "where") token. Here is our complete rule:
$C.enable() => $C.setEnabled(true) :: $C instanceof java.awt.Component;
Meta-Lists
Some rules need to match several statements at a time; one example are
rules to translate references of one variable-arguments method (new to
Java 5) to another such method.
Meta-lists
are similar to meta-variables but have both a beginning and ending '$',
such as "
$list$"
or "
$args$",
and match on zero or more statements. Here is an example which
finds blocks which define a local variable to hold a method's result
value only to immediately return it, and so it eliminates the temporary
variable:
{ $stmts$; $type $value = $expr; return $value; } => { $stmts$; return $expr; }
mapclass
Rules
One important use of Jackpot is for migrating API usage; in other
words, converting source code that references one API to another.
The "
enable() => setEnabled(true)" example above
demonstrates how to move use of one method to another, but sometimes
whole classes get renamed or moved. To avoid having to write a
rule for each field in such a class, a
mapclass
statement is used instead. Its syntax is simple:
mapclass <fully_qualified_old_class> => <fully_qualified_new_class>;
Note: since the Jackpot
engine compares symbols when mapping classes, both the old and new
classes have to be in the project source or classpath so they can be
resolved. If either class cannot be resolved, then this rule will
not find any matches.
Built-In Replacement Actions
note(String note)
|
Display a note regarding the
match in the Results pane.
|
comment(String
comment)
|
Add a comment to the matched
node's source code.
|
transformationFailure(String
note)
|
Report that the rule file
failed, with the note displaying the Results pane.
|
Built-In Guard Expressions
Several boolean expressions are built into the rule language to support
writing guards. Each of these takes a node as a parameter, which
is normally a meta-variable.
Note:
no flow analysis is done during execution of a rule file, so guard
expressions such as
sideEffectFree
or
isTrue only return
true if the immediate node has such a property.
couldThrow(node)
|
True if a method invocation can
throw a declared exception.
|
hasComment(node)
|
True if the node has a comment
associated with it.
|
isConstant(node)
|
True if node is a static final
variable.
|
isLiteral(node)
|
True if node is a primitive type or
string instance. Note: all nodes that return true from
isLiteral() will also be true for isConstant(),
but not necessarily the reverse.
|
isEmpty(node)
|
True if node is either an empty
statement or block.
|
sideEffectFree(node)
|
True if the expression does not
potentially modify variables.
|
referenced(node)
|
True if the variable node is referenced
somewhere or false if it is not.
|
hasVariableDeclarations(node)
|
True if the node is either a
variable declaration, or a block containing variable declarations.
|
isStatement(node)
|
True if the node is a statement,
as defined by the Java Language Specification.
|
isTrue(node)
|
True if the expression is always
true (includes the true
Java constant).
|
isFalse(node)
|
True if the expression is always
false (includes the false
Java constant).
|
isNull(node)
|
True if this is a node for the null Java constant.
|
assignedIn(node,
list)
|
True if the node variable is
assigned to in the statement list.
|
declaredIn(node,
list)
|
True if the node variable is
declared in the statement list.
|
referencedIn(node,
list)
|
True if the node variable is
referenced in the statement list.
|
Miscellaneous Built-In Expressions
parent(node)
|
Returns the parent of a
fully-qualified identifier, such as "foo.bar" from the identifier
"foo.bar.Mumble". |