Search code examples
javaannotations

How to defer getAnnotation call to an instance variable's type


I have an interface

public interface ExportDocumentFactory {
  Class<?>[] getSupportedDataRecordClasses();
}

that allows implementing classes to declare types of DataRecords they support

@Override
public Class<?>[] getSupportedDataRecordClasses() {
  Class<?>[] classes = new Class<?>[1];
  classes[0] = CardRequestBatchComponent.class;
  return classes;
}

so that a service can read the supported types

sourceClasses = sourceFileClientConfig.getFileFactory().getSupportedDataRecordClasses();

This work fine, except that I think it could be done better with an annotation, e.g.

@DataRecordSupport({CardRequestBatchComponent.class})

which looks like

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataRecordSupport {
  Class<?>[] value() default {};
}

and is read like

sourceClasses = sourceFileClientConfig.getFileFactory().getClass().getAnnotation(DataRecordSupport.class).value();

But there's a complication. One of the implementing classes is a facade, so that it overrides like so

@Override
public Class<?>[] getSupportedDataRecordClasses() {
  return config.getFileFactory().getSupportedDataRecordClasses();
}

The getAnnotation method above will fail to find any annotation on objects of this class. How can I get this to work, preferably without changing the caller?

If I did change the caller to only work with this one class, I would first have to create the getConfig getter, and then make it

sourceClasses = sourceFileClientConfig.getFileFactory().getConfig().getFileFactory().getClass().getAnnotation(DataRecordSupport.class).value();

Solution

  • You could make getSupportedDataRecordClasses() a default method, and then have it responsible for reading the annotation from the class. That way the facade can still override the method to call the "real" method, while letting the "real" method continue to be based on the annotation (without having to implement the same thing for every ExportDocumentFactory implementation).

    For example:

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Inherited;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    public interface ExportDocumentFactory {
        
        default Class<?>[] getSupportedDataRecordClasses() {
            var annotation = getClass().getAnnotation(DataRecordSupport.class);
            if (annotation == null) {
                throw new IllegalStateException("no DataRecordSupport annotation present");
            }
            return annotation.value();
        }
        
        @Inherited
        @Retention(RetentionPolicy.RUNTIME)
        @Target(ElementType.TYPE)
        @interface DataRecordSupport {
    
            Class<?>[] value() default {};
        }
    }
    

    Note this is similar to what AbstractProcessor does with e.g., SupportedAnnotationTypes.