JAXB Binding Compiler (XJC) that generates Java classes from XML Schema definitions with both command-line and programmatic APIs
—
The JAXB XJC plugin system provides an extensible architecture for customizing code generation, adding annotations, and implementing domain-specific features through a well-defined plugin interface.
Abstract base class that all XJC plugins must extend to participate in the code generation process.
/**
* Abstract base class for XJC plugins that customize code generation
*/
public abstract class Plugin {
/**
* Get the command-line option name for this plugin
* @return Option name without leading dash (e.g., "fluent-api")
*/
public abstract String getOptionName();
/**
* Get usage description for help output
* @return Multi-line usage description
*/
public abstract String getUsage();
/**
* Main plugin execution method called during code generation
* @param outline Generated code outline containing all classes and fields
* @param opt Compilation options and configuration
* @param errorHandler Error handler for reporting issues
* @return true if plugin execution was successful
* @throws SAXException if plugin execution fails
*/
public abstract boolean run(Outline outline, Options opt, ErrorHandler errorHandler) throws SAXException;
/**
* Parse plugin-specific command line arguments
* @param opt Options object for storing parsed values
* @param args Full command line arguments array
* @param i Current position in args array
* @return Number of arguments consumed (0 if argument not recognized)
* @throws BadCommandLineException if argument is invalid
* @throws IOException if I/O error occurs during parsing
*/
public int parseArgument(Options opt, String[] args, int i) throws BadCommandLineException, IOException;
/**
* Get list of XML namespace URIs that this plugin handles in binding customizations
* @return List of namespace URIs, empty list if none
*/
public List<String> getCustomizationURIs();
/**
* Called when plugin is activated via command line
* @param opts Options object for configuration
* @throws BadCommandLineException if plugin activation fails
*/
public void onActivated(Options opts) throws BadCommandLineException;
/**
* Post-process the schema model before code generation
* @param model Schema model containing parsed information
* @param errorHandler Error handler for reporting issues
*/
public void postProcessModel(Model model, ErrorHandler errorHandler);
}Plugin Development Example:
import com.sun.tools.xjc.Plugin;
import com.sun.tools.xjc.outline.Outline;
import com.sun.tools.xjc.outline.ClassOutline;
import com.sun.tools.xjc.Options;
import com.sun.codemodel.*;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
public class ToStringPlugin extends Plugin {
@Override
public String getOptionName() {
return "Xtostring";
}
@Override
public String getUsage() {
return " -Xtostring : Generate toString() methods for all classes";
}
@Override
public boolean run(Outline outline, Options opt, ErrorHandler errorHandler) throws SAXException {
// Iterate through all generated classes
for (ClassOutline classOutline : outline.getClasses()) {
generateToString(classOutline);
}
return true;
}
private void generateToString(ClassOutline classOutline) {
JDefinedClass implClass = classOutline.ref;
// Generate toString method
JMethod toString = implClass.method(JMod.PUBLIC, String.class, "toString");
toString.annotate(Override.class);
JBlock body = toString.body();
JVar sb = body.decl(outline.getCodeModel().ref(StringBuilder.class), "sb",
JExpr._new(outline.getCodeModel().ref(StringBuilder.class)));
// Add class name
sb.invoke("append").arg(implClass.name() + "{");
// Add field values
boolean first = true;
for (FieldOutline field : classOutline.getDeclaredFields()) {
if (!first) {
sb.invoke("append").arg(", ");
}
sb.invoke("append").arg(field.getPropertyInfo().getName(false) + "=");
sb.invoke("append").arg(JExpr.refthis(field.getPropertyInfo().getName(false)));
first = false;
}
sb.invoke("append").arg("}");
body._return(sb.invoke("toString"));
}
}XJC includes several built-in plugins that demonstrate common customization patterns.
/**
* Built-in plugins provided by XJC
*/
// Accessor plugin for generating additional getter/setter methods
public class AccessorsPlugin extends Plugin {
public String getOptionName() { return "Xaccessors"; }
// Generates additional accessor methods
}
// @Generated annotation plugin
public class GeneratedPlugin extends Plugin {
public String getOptionName() { return "Xgenerated"; }
// Adds @Generated annotations to all generated classes
}
// Code injection plugin for custom code insertion
public class CodeInjectorPlugin extends Plugin {
public String getOptionName() { return "Xinject-code"; }
// Injects custom code from external sources
}
// Episode file generation plugin
public class EpisodePlugin extends Plugin {
public String getOptionName() { return "Xepisode"; }
// Generates episode files for modular compilation
}
// Source location tracking plugin
public class SourceLocationPlugin extends Plugin {
public String getOptionName() { return "Xlocator"; }
// Adds source location tracking to generated classes
}
// Synchronized method generation plugin
public class SynchronizedPlugin extends Plugin {
public String getOptionName() { return "Xsync-methods"; }
// Makes generated methods synchronized
}Plugins are discovered and loaded using Java's ServiceLoader mechanism.
Plugin Registration (META-INF/services/com.sun.tools.xjc.Plugin):
com.example.plugins.ToStringPlugin
com.example.plugins.BuilderPlugin
com.example.plugins.ValidationPluginModule Declaration:
module my.xjc.plugins {
requires transitive org.glassfish.jaxb.xjc;
provides com.sun.tools.xjc.Plugin with
com.example.plugins.ToStringPlugin,
com.example.plugins.BuilderPlugin,
com.example.plugins.ValidationPlugin;
}Plugins can accept command-line arguments for configuration.
/**
* Example plugin with configuration options
*/
public class ConfigurablePlugin extends Plugin {
private String prefix = "generated";
private boolean includeFields = true;
private Set<String> excludedClasses = new HashSet<>();
@Override
public int parseArgument(Options opt, String[] args, int i) throws BadCommandLineException {
String arg = args[i];
if (arg.equals("-Xplugin-prefix")) {
if (i + 1 < args.length) {
prefix = args[i + 1];
return 2; // Consumed current argument and next
} else {
throw new BadCommandLineException("Missing value for -Xplugin-prefix");
}
}
if (arg.equals("-Xplugin-no-fields")) {
includeFields = false;
return 1; // Consumed current argument
}
if (arg.equals("-Xplugin-exclude")) {
if (i + 1 < args.length) {
excludedClasses.add(args[i + 1]);
return 2;
} else {
throw new BadCommandLineException("Missing class name for -Xplugin-exclude");
}
}
return 0; // Argument not recognized
}
@Override
public void onActivated(Options opts) throws BadCommandLineException {
// Validate configuration after all arguments are parsed
if (prefix.isEmpty()) {
throw new BadCommandLineException("Plugin prefix cannot be empty");
}
}
}Model Post-Processing:
@Override
public void postProcessModel(Model model, ErrorHandler errorHandler) {
// Modify model before code generation
for (CClassInfo classInfo : model.beans().values()) {
// Add custom properties or modify existing ones
if (classInfo.getTypeName().getLocalPart().endsWith("Type")) {
// Rename classes ending with "Type"
String newName = classInfo.getTypeName().getLocalPart().replace("Type", "");
// Implementation would modify the class info
}
}
}Customization Processing:
@Override
public List<String> getCustomizationURIs() {
return Arrays.asList(
"http://example.com/xjc/builder",
"http://example.com/xjc/validation"
);
}
@Override
public boolean run(Outline outline, Options opt, ErrorHandler errorHandler) throws SAXException {
for (ClassOutline classOutline : outline.getClasses()) {
// Look for binding customizations
CPluginCustomization customization = classOutline.target.getCustomizations()
.find("http://example.com/xjc/builder", "builder");
if (customization != null) {
// Process the customization
generateBuilderPattern(classOutline, customization);
customization.markAsAcknowledged();
}
}
return true;
}Field-Level Customization:
@Override
public boolean run(Outline outline, Options opt, ErrorHandler errorHandler) throws SAXException {
for (ClassOutline classOutline : outline.getClasses()) {
for (FieldOutline fieldOutline : classOutline.getDeclaredFields()) {
CPropertyInfo property = fieldOutline.getPropertyInfo();
// Check for field-level customizations
if (property.getName(false).startsWith("id")) {
// Generate special handling for ID fields
generateIdFieldMethods(classOutline, fieldOutline);
}
// Modify field annotations
JFieldVar field = getFieldVar(fieldOutline);
if (field != null) {
field.annotate(MyCustomAnnotation.class);
}
}
}
return true;
}Command Line Usage:
# Use built-in plugin
xjc -Xgenerated schema.xsd
# Use custom plugin with options
xjc -Xtostring schema.xsd
# Multiple plugins
xjc -Xgenerated -Xtostring -Xlocator schema.xsd
# Plugin with configuration
xjc -Xbuilder -Xbuilder-prefix=with schema.xsdProgrammatic Usage:
import com.sun.tools.xjc.api.*;
import com.sun.tools.xjc.Plugin;
// Load and configure plugins
SchemaCompiler compiler = XJC.createSchemaCompiler();
compiler.parseSchema(schemaSource);
S2JJAXBModel model = compiler.bind();
// Apply plugins during code generation
Plugin[] plugins = {
new ToStringPlugin(),
new BuilderPlugin()
};
JCodeModel codeModel = model.generateCode(plugins, errorListener);Install with Tessl CLI
npx tessl i tessl/maven-org-glassfish-jaxb--jaxb-xjc