Validate array type attribute in an annotation

Asked

Viewed 479 times

10

When using the annotation Ordem would like to make your attribute valores mandatory. By mandatory I mean that the value of your attribute should not accept an empty array, or an array with an empty string.

It is possible to perform this type of constraint on the attribute of an annotation or this type of constraint is only possible through reflection?

Annotation Ordem:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Ordem {
    String[] valores();
}

Values not accepted:

@Ordem(valores = {}) //array vazio
public class Foo1 {
    private String atributo;
}

@Ordem(valores = {""}) //array com elemento vazio
public class Foo2 {
    private String atributo;
}

Accepted values:

@Ordem(valores = {"atributo"})
public class Foo3 {
    private String atributo;
}

@Ordem(valores = {"atributo1", "atributo2", "atributo3"})
public class Foo4 {
    private String atributo1;
    private String atributo2;
    private String atributo3;
}
  • 1

    It is possible yes using Annotation Processors, but it is somewhat difficult to implement. I don’t have (yet) the knowledge necessary for this, so I leave the hint as a comment instead of a reply. If I can work something out, I’ll give you an answer.

  • 1

    Annotation Professor, a simple example. But I would not add this complexity in order to validate the content of a parameter. Instead, I would create an annotation with expressive name in context indicating clearly what should be passed, and validate at runtime when reading the annotations. Eventually, instead of waiting for a "donkey" array as a parameter you can also create a special type for this parameter that makes it more evident to the programmer which info is required for annotation.

  • Allow me to ask Voce @Victor Stafusa In case the Annotation Processor works only for Source Retention annotations, that is not available in Runtime, is that right? I believe that Geison Santos wants the values of the score in Runtime, right?

  • @Filipegonzagamiranda Works for Runtime too.

  • @Geisonsantos, updated the answer, I hope please send me your feedback, it is yes possible to solve using Java Platform features without third party libraries

1 answer

5


At first when you asked, I immediately remembered my own experiences making notes, and how I was frustrated when trying to create a note that had a validation against its possible values, so my first answer, which is highlighted below, contemplates only the alternatives I myself adopted at the time, which would be to use a different version of the compiler and/or validate the values of the Runtime annotations with unchecked Exceptions. Investigating further I discovered a feature of the Java SE platform available from version 1.6 onwards

Annotation Processor - Java 1.6 or >

It is available as of version 1.6, an API that processes the annotations you define to be validated at compile time, through an interface Processor and an Abstract class AbstractProcessor

How it works?

First of all, no third-party library, plugin or gimmick is required, just write your own teacher and use it when compiling/processing your classes that have the annotations. In it you define which annotations you want to process, and whatever happens, and you can even stop the compilation if you don’t agree with your contracts, issue warnings, or generate codes. It is possible to have more than one Processor. Using a project cycle control tool like Maven you will be able to apply your Processors.

So let’s get to your case

You want to validate the attribute values, you cannot have empty or null strings, you cannot have an empty array, with size(length) == 0

The class below does the work! It is in the package default(without package set) to facilitate understanding and testing

import java.util.Set;

import javax.annotation.processing.Messager;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;

@SupportedAnnotationTypes(value={"Ordem"})
public class MandatoryValuesAnnotationProcessor extends javax.annotation.processing.AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> annotations,
            RoundEnvironment roundEnv) {
        System.out.println("/n/n Processing... /n Processing /n");

        Messager messager = processingEnv.getMessager();

        for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Ordem.class)){
            TypeElement typeElement = (TypeElement)annotatedElement;
            Ordem ordemAnnotation = typeElement.getAnnotation(Ordem.class);
            String valores[] = ordemAnnotation.valores();

            if(valores == null || valores.lenght == 0){
                messager.printMessage(Diagnostic.Kind.ERROR, "Annotation Ordem nao pode ter valores vazios, nao pode ser um array vazio");
                return true;    
            }

            for (String valor : valores) {
                if(valor == null || valor.isEmpty()){
                    messager.printMessage(Diagnostic.Kind.ERROR, "Annotation Ordem nao pode ter strings vazias ou nulas");
                    return true;
                }
            }
        }

        return true;//because we don't want other processors to process this annotations
    }

    @Override
    public SourceVersion getSupportedSourceVersion(){
        return SourceVersion.latestSupported();
    }

}

Above we are defining the Processor for your classes with your annotation, below we use it, already compiled to process your classes.

Compile your Order annotation first, if you sequence the Processor, and then use it to compile the other classes, as you can see, this is not the best way to manage your build clicks so I recommend you use Maven to build your project, or another solution. You can also create a jar with your teacher, here’s where I learned:

http://hannesdorfmann.com/annotation-processing/annotationprocessing101/

For teaching purposes, use this command line when compiling your classes (you can also use Maven)

javac -cp . -Xlint:processing -processor MandatoryValuesAnnotationProcessor -proc:only Foo2.java

Read this reference for a more complete understanding

Reference

--

Note: from the original answer, it is still valid to use a prepared compiler, which is not the best, because it gives you less flexibility and it is also possible to validate attribute values annotations in Runtime, which is common in the SE and EE platform in some Apis, such as JAX-WS, for example

Original Response

No, it is not possible to Geison, at least not only working with the Oracle Hotspot compiler, there are alternative ways, such as creating your own compiler, or customizing one of the Openjdk.

I’ve tried to do what you want to do, besides other things.

What you can do instead is write a unit test using Junit that Reflection, checks the values set for this annotation, not allowing arrays empty or Strings empty.

What I’ve always done is at the moment I need to read the notation values, if these are mandatory, I throw an exception - IllegalArgumentException Where warning the programmer that the value set cannot be null or empty.

  • No, it is not possible Geison, there are no ways to impose rules unless you create your own compiler, or customize one from Openjdk. This is totally wrong. Read the comments they made in the question.

  • Hi @Edgar Muniz Berlinck, good cool, I think this your point is interesting, and I do not ask you arguments to defend it, you have total right to think it wrong to customize a compiler, but here the goal is to answer the question, not defend opinions. Customizing Java platform Features is very common, i.e. JVM Zing, with Garbage Collector without pauses. If you want to chat, call me in chat. Just to finish customize cystomize a compiler is one of the options. Please do not discuss this here.

  • You don’t understand: You said the only way to do this is to change JDK. I’m telling you that this is wrong, there’s the Anotation Processor that’s meant to work with this. So your answer is wrong only on the point I quoted.

  • Ahh, okay, that’s clear. Thank you.

  • 1

    So, and just edit that that gets ok :)

  • Done, @Edgarmunizberlinck Thanks again.

  • I find the discussion here very important and fundamental to correct mistaken convictions or to clarify misunderstandings.

  • 1

    @Filipegonzagamiranda I was reluctant to give his answer as certain because of the statement he made: a strong statement. Strong statements should always be followed by strong justifications. It seems to me now that the correct answer is indeed yours. Using common and ordinary language resources it is not possible to do the type of restriction described in the question. For this it is necessary to make use of more complex resources that escape the everyday use of language.

  • Thanks for the feedback. It’s up to them that we build a strong and concise community. Yes, everything indicates that yes, but I’m still going to double check the Annotation Processor, it’s not clear to me. It seems that it only processes Retention.SOURCE annotation, and that’s not what we want, we want the process to occur with Retention.RUNTIME annotations

  • @Filipegonzagamiranda, I tried to run the example with the javac -cp command. -Xlint:Processing -Processor Mandatoryvaluesannotationprocessor -proc:only Foo2.java, but it didn’t work. Submitted message "error: Annotation Processor 'Mandatoryvaluesannotationprocessor.java' not found Warning: Annotation Processing without Compilation requested but no Processors Were found."

  • You need to compile your annotation, compile your database, and then use it to compile subsequent classes. So the ideal maybe for a large project were you to use Maven to manage it, there is another way tb that is creating a separate jar with your processes and pass it pro compiler, or update the response.

Show 6 more comments

Browser other questions tagged

You are not signed in. Login or sign up in order to post.