Sunday, September 8, 2013

Creating an XJC Plugin to instantiate Lists

In my previous post, I described how you can create an XJC Plugin and how to use it from maven. I will briefly go over the steps one more time, before explaining how to find and instantiate lists.

Creating the Plugin:

The steps are:

  1. Create a new Maven project, and add the jaxb-xjc dependency to your pom.
  2. Create a Class that extends com.sun.tools.xjc.Plugin.
  3. Implement the method getOptionName() to return the Option name you want used to turn on the plugin.
  4. Implement the logic of the plugin in the run() method.
  5. Go to (or create) the folder src/main/resources/META-INF/services in your project.
  6. In folder services, create a file named "com.sun.tools.xjc.Plugin"
  7. In that file, add the fully qualified name of your Plugin class.
For the detailed steps check my previous post.


The Implementation

Override the run() method with the following:


 @Override
 public boolean run(Outline outline, Options opt, ErrorHandler errorHandler)
   throws SAXException {
    
    for (ClassOutline co : outline.getClasses()){
     //2. Look through the fields defined in each of the classes
     Map<String, JFieldVar> fields = co.implClass.fields();

     for (JFieldVar f : fields.values()){
      
      JClass fClass = (JClass) f.type();
      
      if (fClass.getTypeParameters()!=null && 
        fClass.getTypeParameters().size()==1){
       
       // f.type() is a list
       JType inner = fClass.getTypeParameters().get(0);
       
       
       f.init(JExpr._new(co.parent().getCodeModel()
         .ref(ArrayList.class).narrow(inner)));
       
       replaceGetterNoInst(co, f); 
      }
      
     }
    }
  return true;
 }

In the run() method, we iterate over the fields in a class, and check if they are parameterized or generic. If they have exactly one type parameter, we conclude its a list.

Once a list is found, we can instantiate the field with the init() method. The rest of the expression is:

  • JExpr is a factory class that helps generate several JExpressions
  • _new() creates the new operator
  • ref() returns a JType for the given class
  • narrow() sets the parameter type.
One last thing to remember, the Plugin actually runs after XJC is done generating the classes. This means that the getter method for the field just initialized already exists. And as you probably know, the getter method for a List always checks if the field is null and if it is, it instantiates it. 

Now this of course has no negative effect what so ever, however it is unnecessary. I therefore added the method replaceGetterNoInst(), which you've probably noticed in the code  above.

What it does is find the getter for the list and remove it. Then add a new one that does not check if the List is null. Here it is:
/**
  * Replaces the getter of field f, in classOutline co with one that does not
  * check if the field is null.
  * 
  * @param co the ClassOutline of the class in which the getter is replaced
  * @param f the Field for which the getter is replaced
  */
 private void replaceGetterNoInst(ClassOutline co, JFieldVar f) {
  //Create the method name
  String get = "get";
  String name  = f.name().substring(0, 1).toUpperCase() 
    + f.name().substring(1);
  String methodName = get+name;
  
  //Find and remove Old Getter!
  JMethod oldGetter = co.implClass.getMethod(methodName, new JType[0]);
  co.implClass.methods().remove(oldGetter);
  
  //Create New Getter
  JMethod getter = co.implClass.method(JMod.PUBLIC, f.type(), methodName);
      
  getter.body()._return(JExpr.ref(f.name()));
 }

Done!

So there you have it. If you'd like to take a look at the source code for the Plugin, check out my GitHub Repo at https://github.com/walmir/xjc-inst

If you've got any questions feel free to comment here. If you find bugs or have improvement ideas/requests please submit them using the issue tracker at github.

2 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. This assumes that lists are the only class with type parameters, which is fairly limiting as many bindings will include JAXBElement<> wrappers on some objects. There should be a fairly easy way to check that it's actually a list instead.

    ReplyDelete