Search code examples
javajaxbxjcsun-codemodeljcodemodel

JAXB, XJC: Generating field to Class Outline


My question concerns writing JAXB plugins, in particular ClassOutline internals.

In com.sun.tools.xjc.outline.ClassOutline there are fields:

  • target
  • ref
  • implClass
  • implRef

Code:

/**
 * This {@link ClassOutline} holds information about this {@link CClassInfo}.
 */
public final @NotNull CClassInfo target;

/**
 * The exposed aspect of the a bean.
 *
 * implClass is always assignable to this type.
 * <p>
 * Usually this is the public content interface, but
 * it could be the same as the implClass.
 */
public final @NotNull JDefinedClass ref;

/**
 * The implementation aspect of a bean.
 * The actual place where fields/methods should be generated into.
 */
public final @NotNull JDefinedClass implClass;

/**
 * The implementation class that shall be used for reference.
 * <p>
 * Usually this field holds the same value as the {@link #implClass} method,
 * but sometimes it holds the user-specified implementation class
 * when it is specified.
 * <p>
 * This is the type that needs to be used for generating fields.
 */
public final @NotNull JClass implRef;

As far as I know (SO Answer):

  • target - holds information in Model, which represents parsed and analysed schema file (.xsd)
  • ref is usually equals to implClass and both holds Code Model
  • implClass is the right place to put newly generated fields, method, etc.
  • implRef - what is it?

I want to add new field to class described by ClassOutline, so the code looks like this:

JDefinedClass dstClass = classOutline.ref;
JFieldVar dstField = dstClass.field(srcField.mods().getValue(),
                        srcField.type(), srcField.name());

It works great, until there is another plugin which works after above code is executed and uses com.sun.tools.xjc.outline.ClassOutline.getDeclaredFields() method.

Imagine - Plugin1 creates new fields and then CopyablePlugin is executed and wants to add clone() method, which copy every field. But CopyablePlugin doesn't see fields newly generated by Plugin1 - because to retrieve all fields from ClassOutline the CopyablePlugin uses com.sun.tools.xjc.outline.ClassOutline.getDeclaredFields() method which looks like:

/**
 * Gets all the {@link FieldOutline}s newly declared
 * in this class.
 */
public final FieldOutline[] getDeclaredFields() {
    List<CPropertyInfo> props = target.getProperties();
    // ...

Notice, that getDeclaredFields() retrieves properties from ClassOutline.target field (this's the Model - parsed XSD schema) and completely ignores code generated to ClassOutline.implClass.

Is it a bug or a feature?

For now I found workaround. The same field is also added as property to target:

classOutline.target.addProperty(prop);

Questions

  1. Could you explain me, what is the role of ref/implClass/implRef?
  2. Where I should generate completely new fields/method? Into ref/implClass?
  3. Does it necessary to keep consistency between ref/implClass and target? New field added to implClass should be also added to target, right?
  4. Is com.sun.tools.xjc.outline.ClassOutline.getDeclaredFields() correct? Or How properly retrieve all fields from ClassOutline? Maybe this should be union of target and implClass content?

Solution

  • I would advise against adding fields to ClassOutline. It is too late and is too much trouble. Believe me, I've tried it.

    What I found to be much easier and much more elegant is adding properties to CClassInfo. It is one stage earlier, here you manipulate the model. Then on the next step the outline will be generated from the model so you don't even have to care about adding anything to ClassOutline.

    To add property to the CClassInfo you just instantiate the property (for instance new CAttributePropertyInfo(...)) and then just add it to the CClassInfo: myClassInfo.addProperty(myPropertyInfo);.

    In the case of the Copyable plugin, if your plugin adds properties to the model and your plugin is invoked before the Copyable plugin, the latter will see the newly added fields. This is exactly why I recommend augmenting the model, not hack the outline or the code model.

    Now to your questions.

    1. ref/implClass/implRef are all normally just the target generated class. The separation is due to some generation modi where XJC generated "public interface" and "implementing class" separately. I don't know what implRef is, the JavaDoc says something about user-specified classes so I'm thinking about jaxb:class/@ref binding. Never dealt with it, though.

    2. The best would be to augment the model (if you want to add new properties/fields). If you want to add some model-unrelated code (like clone method from Copyable plugin), I'l just add it to classOutline.implClass.

    3. Technically there is no need to keep the code model (classOutline.implClass) and the model (classOutline.target) in sync. If you don't, this won't break XJC. I can imagine that this might break some XJC plugins. For instance, a plugin may iterate over fields in JDefinedClass and try to find corresponding model properties. But the danger is rather theoretical.

    4. ClassOutline.getDeclaredFields() is correct, but it only gives you fields declared in this class - without fields from the superclass. But it's quite trivial to gather all of the fields recursively. getDeclaredFields() produces FieldOutlines based on the properties from target CClassInfo. Forget the implClass, that's just a representation of the ClassOutline. So "union of target and implClass" does not make much sense.