Wednesday, July 24, 2013

Hitting the Jackpot

In many cases we, as developers, will come across a situation where we need to refactor lots of code in a very specific way. To explain I'll use a recent example I've run into... upgrading an enterprise application for Java EE 7. Java EE 7 applies more constraints onto the PostConstruct method, specifically preventing you from throwing checked exceptions from the method. Glassfish 4 fails deployment with the following message:

Exception while deploying the app [my.fake.company.enterpriseportal_application_war_2.7.0-A8-SNAPSHOT] : The lifecycle method [initialize] must not throw a checked exception. Related annotation information: annotation [@javax.annotation.PostConstruct()] on annotated element [public void my.fake.company.enterpriseportal.ui.admin.livecycle.ecn.MissingEcnJdbcDao.initialize() throws my.fake.company.enterpriseportal.ui.admin.livecycle.ecn.MissingEcnDaoException] of type [METHOD]

Well, the good news here is that it's a very explicit message. I know exactly what I have to do, but how many places have we done just that? Glassfish tells me about one instance of this problem, but there might be many scattered through our 50+ artifacts. Now we could fix the problem by hand, scouring each of our libraries one at a time looking for this problem, or we could hit the  Jackpot . Jackpot is a tool developed by one of the NetBeans developers that allows for large-scale refactoring from the command line.

How it works?

At it's heart Jackpot is a fairly simple tool, once you figure out the ins and outs. The first being, don't try to build it from source code, the build available for download may be slightly out of date but will save you some large headaches; trust me. Go ahead, download it. Once you have it downloaded and unzipped you can execute it on your source code with a command like 

jackpot --hint "System out / err" src/main/java/
For a list of all available hints run "jackpot --list"

In this example we're using one of the built-in features of Jackpot. Since it is somewhat integrated with NetBeans, all the NetBeans refactoring patterns are available to you via the "–hint" argument. This hint checks for any lines that print directly to System.out or System.err; but that's not that helpful.  Firstly, this will only tell you about the problems, it's not going to fix them; and secondly, that's not what we're looking for! Let's talk about automatic refactoring first... because it's easier. To have jackpot automatically fix any issues that it can, you just add the "–apply" argument to you command, so you'll have something like this

jackpot --apply --hint "System out / err" src/main/java/

In this example the offending lines will simply be removed. I hope you didn't do any real computing in a println command!

Custom Rules

The rules formatting has been integrated into Netbeans as of NetBeans 7.1, but I'm not sure where it's used.

Now, to fix our problem. No rule that NetBeans provides will fix our problem, instead we'll need to create our own. The formatting of rules is specific to Jackpot/NetBeans. The documentation available is a little scarce, I've searched through the NetBeans site and the wiki space and have found only these four pieces of documentation:

  1. https://bitbucket.org/jlahoda/jackpot30/wiki/RulesLanguage
  2. https://bitbucket.org/jlahoda/jackpot30/wiki/RulesLanguageAdditionalDocs
  3. https://bitbucket.org/jlahoda/jackpot30-demo-examples/src/0f396ef509cb5ae88933abf50e39442e8aea7923/src/META-INF/upgrade/j.l.Thread.sleep.hint?at=default
  4. http://webcache.googleusercontent.com/search?q=cache:Bej5PnlBPH8J:netbeans.org/kb/docs/java/editor-inspect-transform.html+&cd=2&hl=en&ct=clnk&gl=ca (the NetBeans site was down while I was trying to access it)

Each of these documents only provide a small amount of information regarding the rules, but it was enough to get me going. The rules are divided into two main pieces, the pattern to match and the fix-pattern to apply. If you're only searching and not planning on fixing, you don't need the fix-pattern section, but if you run jackpot with "–apply" it will remove the lines, that's the default action. 

Pattern Matching

The first section, the pattern matching section, is used to tell the system what needs to be found for this rule. In our case we want to look for all methods that are annotated with @PostConstruct. Jackpot is not looking for the specific text "@PostConstruct" and if you set a rule with that text it wouldn't likely find anything. That's because it requires fully qualified class names (unless using custom imports, but let's not go there). This means we're really looking for @javax.annotation.PostConstruct. OK, that's easy enough, but we also need to look for the method, because we need to see if it has an exception too.

To look for the method we'll need two different types of variables. The different variable types are described in this document. The two types we need are:

This rule only catches PostConstruct methods that throw a single exception, you can use $exception$ to catch multiple exceptions, but it can't be used for the fix-pattern.
  1. The "any expression" one, simply noted by using the $ at the beginning of the variable name. This type of variable will be used for the method name and the exception type that is thrown.
  2. The "any number of statements" one, noted by a $ at the beginning an $; at the ned of the variable name. This type of variable will be used for the method body.

Using this information about variables, we'll end up with a pattern like

@javax.annotation.PostConstruct
public void $name() throws $exception { $stmts$; }
;;

Note the ";;" at the end of the rule. This deinfes the end of the rule and emans you can have multiple rules in one file. The rule we created is likely fine and will likely only catch what we're looking for anyway, but, the Exception we're trying to deal with specifically says checked exceptions. If we defined a method that "throws MyRuntimeException" in the definition for documentation purposes, we'd catch that method as well. So far we haven't seen a way to deal with that, so... introducing "Conditions."

Conditions can be added to the end of both sections, I'll explain how conditions work on fix-patterns later, but for now lets focus on the pattern for matching. A condition helps determine if the pattern actually does match by providing more validation capabilities. In our case we'll check the type of the "exception" variable. For more conditions see this document. In Java to check the type of an object against a class we use "instanceof," and in the Jackpot rule language we do the same. Conditions are defined at the end of the rule so our new rule looks like this

@javax.annotation.PostConstruct
public void $name() throws $exception { $stmts$; } :: $exception instanceof java.lang.Exception
;;

Now, we can run this rule right now using 

jackpot --hint-file /path/to/rule src/main/java/

This will list all occurrences that match our custom rule.

Fixing the Problem

To start the fix pattern of the rule you use the "=>" on a new line. When defining the fix you can reuse the variables that you defined in the pattern matching section of the hint. In our case, we'd just like to catch whatever exception is thrown and maybe print it out. Our fix for this pattern should look something like:

public void $name() {
    try{
       $stmts$;
    }catch($exception ex){
       ex.printStackTrace();
    }
}

which means our resulting rule will be:

@javax.annotation.PostConstruct
public void $name() throws $exception { $stmts$; } :: $exception instanceof java.lang.Exception
=>
public void $name() {
   try{
      $stmts$;
   }catch($exception ex){
      ex.printStackTrace();
   }
}
;;

I mentioned earlier that you can apply conditions to fix-patterns as well and I wasn't lying. The truth is that you can have many fixes and Jackpot will use the first applicable fix. This is not applicable in this scenario, but if we hop over to the Jackpot demos we can look at j.l.String.hint. This rule defines two different ways to fix the problem, one if the JDK is greater than or equal to JDK6 and one to use otherwise. Each fix gets started with the "=>" delimiter and have conditions appended to them with the "::" operator.

Conclusion

Jackpot is very powerful, and even more powerful when you consider the scripting capabilities. For example, we could write a script to download all of our repositories and crawl through them fixing new problems we have defined. Whether those new problems are based on upgrade woes as described above, or simply to deal with API changes we make within our code we can automatically refactor all of our code and not miss any required change.