1

I have sort of a deep class hierarchy and i want to tell JAXB to bind all the classes. I have sort of the following :

@XmlSeeAlso(B.class)
Class A {}
@XmlSeeAlso(C.class)
Class B extends A{}
@XmlSeeAlso(D.class,E.class,...)
Class C extends B{}
Class D extends C{}; Class E extends C{} ... and so on 

Is there any way i can get to bind all these classes without using the @XmlSeeAlso in every super class and without mentioning all subclasses because i have many.

Ondřej Fischer
  • 411
  • 1
  • 7
  • 16
  • If you want runtime solution, then Java reflection API doesn't allow it. You'd have to deeply analyze the jars, but that would be a hell. But you can try compile time annotation processing, generating the jaxb index file. Not sure from top of my head how, but it should be possible. – Ondřej Fischer Aug 17 '18 at 12:18
  • @OndřejFischer generating the jaxb index file would be the same as what i described i have to mention all classes. I'm looking for a dynamic alternative that does that sort of automatic. – Saad Hamouine Aug 21 '18 at 09:35
  • What I'm talking about is only implementing annotation processor, which tests every class in the compiled sources, if it's a subclass of any your root classes, and if it is, then automatically add it to generated index file. So it would be automatic solution. You will not need to maintain such index file. I'll try to elaborate on that in an answer. – Ondřej Fischer Aug 21 '18 at 18:52

1 Answers1

0

As mentioned in comments, Java doesn't support requested feature of getting all subclasses at runtime viaa reflection.

But it should be possible to inspect all classes in a project at it's compilation time, and generate a jaxb.index in your jar file.

An example (not complete, thus not directly working, but to demonstrate the idea) of such annotation processor can look like this:

@SupportedAnnotationTypes("*")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class JaxbProcessor extends AbstractProcessor {

    @Override
    public synchronized void init(ProcessingEnvironment env) {
        super.init(env);

        // Java 8 compiler plugin API to hook on compilation of every single class.
        JavacTask.instance(env).addTaskListener(new TaskListener() {

            // Prepare the writer
            PrintWriter writer = new PrintWriter(env.getFiler().createResource(/* Details of output jaxb.index file */).openWriter());
            Set<TypeElement> jaxbParents = new HashSet<>();

            @Override public void started(TaskEvent taskEvent) {
                // Nothing needs to be done here.
            }

            @Override public void finished(TaskEvent taskEvent) {
                if(taskEvent.getKind() == ANALYZE) {

                    // This is where the compiler invokes our code.
                    // Side effect of this inspection is to collect all classes, that should be included in our jaxb.index
                    // into the jaxbParents set.
                    inspect(taskEvent.getTypeElement());

                    // Now simply write it to the file (output details to be provided).
                    // We should actually only write down difference from previous invocation. Let me fix it later.
                    jaxbParents.forEach(writer::println);
                }
            }

            private void inspect(TypeElement type) {

                // First inspect current class element
                testForJaxbParent(type);

                // Do not forget to inspect also inner classes.
            type.getEnclosedElements().stream().filter(TypeElement.class::isInstance).map(TypeElement.class::cast).forEach(this::testForJaxbParent);
            }

            /**
             * Test if the type should be added to JAXB index file.
             */
            private boolean testForJaxbParent(TypeElement type) {
                if(jaxbParents.contains(type)) {
                    // It's already in the set, so no need to bother with it.
                    return true;
                }
                if(type.getAnnotation(JaxbRoot.class) != null || testForJaxbParent((TypeElement) env.getTypeUtils().asElement(type.getSuperclass()))) {
                    // If our type is annotated with our special "extension" to JAXB - JaxbRoot, it means, that it is our
                    // root class, that needs to be added to the jaxb.index.
                    //
                    // If it is not annotated, then still test hierarchy of superclasses recursively, and if there is any
                    // superclass being the root, then add it including all children on the stack at the return from the
                    // recursion.
                    return jaxbParents.add(type);
                }
                return false;
            }
        });
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        // No real annotation processing needed.
        return false;
    }
}

Once you have the annotation JaxbRoot and this processor in a jar, and ideally also the service descriptor to let Java automatically find this processor in the jar, then simply add the jar to your classpath, annotate only your root class, and you'll get generated jaxb.index with all it's subclasses.

And even if you have your project split into multiple jars, and have your root class in one, and children in another, still the processor get's invoked and generates the index file per jar. Then you'll just have to merge them all together, which can be just one utility class delivered together with the processor.

Ondřej Fischer
  • 411
  • 1
  • 7
  • 16