3

I would like to create a static nested class using annotation Processor. Is it possible?

I have created @MyAnnotation annotation:

package annotationprocessing;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}

and annotation processor:

package annotationprocessing;

import java.io.BufferedWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;
import javax.tools.JavaFileObject;

@SupportedAnnotationTypes({"annotationprocessing.MyAnnotation"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends javax.annotation.processing.AbstractProcessor {

    private Filer filerUtils;
    private Elements elementUtils;
    private TypeElement myAnnotationTypeElement;
    private Map<TypeElement, List<VariableElement>> annotatedFields;

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

        filerUtils = processingEnv.getFiler();
        elementUtils = processingEnv.getElementUtils();
        myAnnotationTypeElement = elementUtils.getTypeElement(MyAnnotation.class.getCanonicalName());
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        annotatedFields = new HashMap<>();

        roundEnv.getElementsAnnotatedWith(myAnnotationTypeElement)
                .stream()
                .map(element -> (VariableElement) element)
                .forEach(this::processAnnotation);

        if (annotatedFields.isEmpty()) {
            return true;
        }

        System.err.println(annotatedFields);

        for (Map.Entry<TypeElement, List<VariableElement>> entry : annotatedFields.entrySet()) {
            TypeElement enclosingClass = entry.getKey();

            try {
                JavaFileObject javaFileObject = filerUtils.createSourceFile(enclosingClass.getQualifiedName().toString() + "$Nested");
                try (BufferedWriter writer = new BufferedWriter(javaFileObject.openWriter())) {
                    if (elementUtils.getPackageOf(enclosingClass).getQualifiedName().length() > 0) {
                        writer.write("package " + elementUtils.getPackageOf(enclosingClass).getQualifiedName() + ";");
                        writer.newLine();
                    }
                    writer.write("public /*static*/ class " + enclosingClass.getSimpleName() + "$Nested {");
                    writer.newLine();
                    for (VariableElement varElement : entry.getValue()) {
                        writer.write("static int " + varElement.getSimpleName() + ";");
                        writer.newLine();
                    }
                    writer.newLine();
                    writer.write("}");
                }
            } catch (IOException ex) {
                Logger.getLogger(MyAnnotationProcessor.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        return true;
    }

    private void processAnnotation(VariableElement sharedElement) {
        TypeElement enclosingClass = (TypeElement) sharedElement.getEnclosingElement();
        annotatedFields.putIfAbsent(enclosingClass, new ArrayList<>());
        annotatedFields.get(enclosingClass).add(sharedElement);
    }
}

While compiling class (javac -processor annotationprocessing.MyAnnotationProcessor -cp annotationprocessing.jar TestClass.java)

package org.full.path;
import annotationprocessing.MyAnnotation;
public class TestClass {

    public static class OtherNested {
        static int staticInt;
    }

    @MyAnnotation
    int staticInt;
    public static void main(String[] args) {
        System.out.println("other: " + TestClass.OtherNested.staticInt);
        //System.out.println("not working: " + TestClass.Nested.staticInt);
        System.out.println("generated: "+TestClass$Nested.staticInt);
    }
}

the MyAnnotationProcessor processes the source file and generated TestClass$Nested.class file, but it can be only accessed by using TestClass$Nested name instead of TestClass.Nested like in nested classes. Moreover, I cannot use static keyword in the generation code (because it is fully treated as highest level class).

Maybe there is a way to fully rewrite input source code with addition of static nested class?

faramir
  • 289
  • 3
  • 13
  • I know this is old but you can write a static nested class if you use the following fix: `filerUtils.createSourceFile(elementUtils.getBinaryName(enclosingClass) + "$Nested")` – Christian Schlichtherle Jan 10 '21 at 19:01

1 Answers1

1

Creating a source file for the nested class does not seem like the right approach here - if the generated source file is compiled it will be treated as an top-level class, even if the name looks like that of a nested class.

Nested and enclosing classes reference each other in their bytecode. From JVM Specification, Chapter 4.7.6. The InnerClasses Attribute, about the classes[] item:

In addition, the constant_pool table of every nested class and nested interface must refer to its enclosing class, so altogether, every nested class and nested interface will have InnerClasses information for each enclosing class and for each of its own nested classes and interfaces.

So you could probably create a .class file for the nested class (with the correct bytecode structure for inner classes) and then use some bytecode manipulation library to change the implementation of the enclosing class.

You are actually not supposed to changing existing classes using annotation processors. It seems to be possible anyway though, as explained here.

All that seems like it requires a lot effort. I would avoid all this if possible and try different approaches. It's hard to tell what your use case is, but maybe generating a subclass of TestClass and put your static class into the subclass or something like that.

kapex
  • 28,903
  • 6
  • 107
  • 121