Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to create static nested class using annotation Processor?

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?

like image 727
faramir Avatar asked Apr 30 '26 19:04

faramir


1 Answers

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.

like image 193
kapex Avatar answered May 02 '26 09:05

kapex



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!