In my last blog post, Extending XJC Funcionality With a Custom Plugin, I outlined how to use custom code to make wholesale changes to all classes in an XJC build session. In this installment, I'll show how to narrow the scope of changes to a single class, property, or method.

In the previous example, the plugin looped through the classes that made up the XJC build without regard to whether any customization tags were present in the original XSD or an external bindings file. In this example, the plugin will honor changes only where they're indicated by customization tags in the external bindings file (bindings.xjb in the attached archive). The contents of that file are:

<> jxb:extensionbindingprefixes="xjc tpi" version="2.1" xmlns:jxb="http://java.sun.com/xml/ns/jaxb" xmlns:tpi="http://com.captech/blog/xjcplugin/surgical" xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc" xmlns:xs="http://www.w3.org/2001/XMLSchema">
 <>>
 <>>>
 <> uid="1">>
 >
 
 <> schemalocation="Person.xsd">
 <> removeclass="com.captech.person.Extra">>
 <> node="//xs:complexType[@name='Name']">
 <>>
 <>>
 
 >
 >
 <> getlastname="public synchronized" setfirstname="public synchronized">>
 <> setlastname="public synchronized">>
 <> node=".//xs:element[@name='FirstName']">
 <> modifiers="private">>
 >
 >
 >
>

Notice that the test plugin namespace is declared and referred to in the outermost bindings tag jxb:extensionBindingPrefixes="xjc tpi". This lets XJC know that any elements found in the document that have this namespace are customizations. The interesting bits start after the jxb:bindings tag that refers to the Person schema. The 'tpi:package' line is the first customization. Since it is a child element of the schema binding, it will appear bound to that sort of object in the code model.

The second customization tag, , appears inside a binding that specifies the ComplexType Name in the Person XSD, and will be attached to that class in the model. The next customization tpi:class tag is also attached to the same spot in the model - having two tags simply highlights that there can be as many tags as you like on any particular node.

The final customization tag, tpi:property, is bound to the FirstName element within the Name ComplexType (within the Person Schema).

The goal of this plugin is to be able to remove whole classes from the build and alter method and property signatures only where indicated by customization tags.

Preparing To Strike

As in the previous example, there is a bit of boilerplate that returns the name of the command line option and usage information.

@Override
public String getOptionName() {
 return "Xsurgical";
}
 
@Override
public String getUsage() {
 return " -Xsurgical: manipulate classes";
}

Since this example uses customization tags, there are a couple of extra methods that must be implemented:

private static final String SURGICAL_NAMESPACE = "http://com.captech/blog/xjcplugin/surgical";
 
@Override
public List String> getCustomizationURIs() {
 return Collections.singletonList(SURGICAL_NAMESPACE);
}
 
public boolean isCustomizationTagName(String nsUri, String localName) {
 return nsUri.equals(SURGICAL_NAMESPACE);
}

The first additional method is called by XJC to map plugins to customization tag namespace(s). The second method is called by XJC when it encounters a tag in a bindings file that isn't its own and is in one of the namespaces of one of its plugins. This method verifies that the tag in question is in fact recognized by the plugin that returned the given namespace in the first method.

As before, the main workhorse method is the 'run' method. It is called when the model- and code-generation is complete, but before the code is committed to Java files.

In the run method, Schema customizations are process first. The code then loops through the ClassOutlines processing their customizations, and then loops through each field in the class, processing their customizations.

@Override
public boolean run(Outline outline, Options opts, ErrorHandler errHandler)
 throws SAXException {
 
 processSchemaTags(outline);
 
 for( ClassOutline co : outline.getClasses() ) {
 processClassTags(co);
 
 FieldOutline fos[] = co.getDeclaredFields();
 for (FieldOutline fo : fos) {
 processPropertyTags(fo);
 }
 }
 
 return true;
}

To process Schema customizations, the code loops through the top-level customization objects looking for 'my' tag named 'package'. Once one is found it is marked as acknowledged, and the code proceeds to find the attribute on the tag named 'removeClass'. If found, the value of that tag is looked up in the CodeModel outline. If that class name is found, it is removed from the code model. Normally this would be sufficient, but because XJC also generates an ObjectFactory, the associated 'createXXXX' method must also be deleted. The rest of the processSchemaTags method finds and eliminates methods on the ObjectFactory that end in the short name XXXX of the removed class.

private void processSchemaTags(Outline outline) {
 CCustomizations ccs = outline.getModel().getCustomizations();
 for (CPluginCustomization pc : findMyCustomizations(ccs, "package")) {
 
 pc.markAsAcknowledged();
 
 String classToRemove = pc.element.getAttribute("removeClass");
 if (classToRemove != null) {
 JDefinedClass clazz = outline.getCodeModel()._getClass(classToRemove);
 if (clazz != null) {
 clazz._package().remove(clazz);
 
 // Zap createXXX method from ObjectFactory
 String removedClassName = clazz.name();
 String factoryName = clazz._package().name() + ".ObjectFactory";
 JDefinedClass objFactory = outline.getCodeModel()._getClass(factoryName);
 Iterator JMethod> methodIterator = objFactory.methods().iterator();
 while (methodIterator.hasNext()) {
 JMethod method = methodIterator.next();
 if (method.name().endsWith(removedClassName)) {
 methodIterator.remove();
 break;
 }
 }
 }
 }
 }
}

To process class-level customizations, each ClassOutline's customizations are searched for our custom tags (in this case the code will claim any non-namespaced tag as its own, since the name of the attribute is used as the name of a method). For each of the custom tags, processMethodModifiers is called to set the modifiers (synchronized, static, final, private, protected, and public) on the given method.

private void processClassTags(ClassOutline co) {
 CCustomizations ccs = co.target.getCustomizations();
 for (CPluginCustomization pc : findMyCustomizations(ccs, "class")) {
 
 pc.markAsAcknowledged();
 
 NamedNodeMap attribs = pc.element.getAttributes();
 if (attribs != null) {
 for (int i = 0; i &lt; attribs.getLength(); i++) {
 Node n = attribs.item(i);
 if (n.getNamespaceURI() == null) {
 // One of ours - we could've prefaced the attribute with
 // our namespace.
 processMethodModifiers(co, n.getNodeName(), n.getNodeValue());
 }
 }
 }
 }
}

Processing property tags is similar to processing class-level method tags; the only real difference is that the 'property' tag has its own attribute named 'modifiers' -- the property tag refers to exactly one property.

private void processPropertyTags(FieldOutline fo) {
 CCustomizations ccs = fo.getPropertyInfo().getCustomizations();
 for (CPluginCustomization pc : findMyCustomizations(ccs, "property")) {
 
 pc.markAsAcknowledged();
 
 String modifiers = pc.element.getAttribute("modifiers");
 if (modifiers != null) {
 processPropertyModifiers(fo.parent(), fo.getPropertyInfo().getName(false), modifiers);
 }
 }
}

Pre-Strike Recon

Without the plugin and bindings file referenced on the command line, the class Extra is generated, and the class Name has the following property and method modifiers:

protected String firstName;
protected String middleInitial;
protected String lastName;
 
public String getFirstName() {
 return firstName;
}
 
public void setFirstName(String value) {
 this.firstName = value;
}
 
public String getMiddleInitial() {
 return middleInitial;
}
 
public void setMiddleInitial(String value) {
 this.middleInitial = value;
}
 
public String getLastName() {
 return lastName;
}
 
public void setLastName(String value) {
 this.lastName = value;
}

The Aftermath

Using the plugin and bindings file, the Extra class is absent and unreferenced from the ObjectFactory, and that the Name class now has the following property and method modifiers:

private String firstName;
protected String middleInitial;
protected String lastName;
 
public String getFirstName() {
 return firstName;
}
 
public synchronized void setFirstName(String value) {
 this.firstName = value;
}
 
public String getMiddleInitial() {
 return middleInitial;
}
 
public void setMiddleInitial(String value) {
 this.middleInitial = value;
}
 
public synchronized String getLastName() {
 return lastName;
}
 
public synchronized void setLastName(String value) {
 this.lastName = value;
}

Post-Mortem

As you can see, the plugin successfully modified the generated classes only where directed by a tag-driven surgical strike. As before, there is a zip file attached that contains Eclipse projects that will allow you to test-drive the example, as well as screenshots of how to run XJC from a debug run configuration so that you can insert breakpoints in your own plugin code to see what's happening under the covers.