Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Annotations on variables declared in try-with-resources?

Tags:

Just wondering what annotations could be used with variables declared in try-with-resources statements, which is allowed as per its grammar. The section 14.20.3 from the language specification (Java 7) reads,

TryWithResourcesStatement:
    try ResourceSpecification Block Catchesopt Finallyopt

ResourceSpecification:
    ( Resources ;opt)

Resources:
    Resource Resource ; Resources

Resource:
    VariableModifiersopt Type VariableDeclaratorId = Expression

And VariableModifiers expands as (section 14.4),

VariableModifiers:
    VariableModifier
    VariableModifiers VariableModifier

VariableModifier: one of
    Annotation final

There you go: VariableModifier can have Annotation. Well, that basically means, we can write something like this:

try( @SomeAnnotation SomeType obj = createSomeType() ) { 
  //some code
}

So my question is: how and what kind of annotations could possibly be used in try-with-resources and to achieve what kind of behaviors? Any innovative idea? Have anybody used them?

like image 434
Nawaz Avatar asked Aug 02 '17 15:08

Nawaz


1 Answers

Not in Java 7, but I suspect you tagged this java-7 only because that's the version that introduced try-with-resources, and you are still interested in possible uses beyond Java 7 (I think this question is very interesting for Java >= 8).

I think there is nothing special that binds try-with-resources and annotations, it is not a special case in the grammar; in this respect such variables (declared within a try-with-resources statement) are just the same as other local variables, and the grammar allows annotations just as well:

  • Java 7 has introduced try-with-resources statements, in which you can declare a variable that will get special treatment.
  • The grammar has been allowing annotations on local variable declarations as early as Java 5, when annotations were introduced (but we had to wait Java 6 to get a useable API for annotation processing)
  • But even with Java 7 it was not possible for annotation processors to access annotations on local variables. The only annotation on local variable that was "useable" was @SuppressWarnings but that one was treated specially by the compiler itself, there was no way for you to hook into this.
  • Java 8 introduced a new kind of annotation context besides "declaration context", there is now "type context", and now an annotation Target can be ElementType.TYPE_USE

So the answer (with Java 8) is the same as with any annotation on local variables.


(some trivia about Java 8's new "type annotations")

... and this is where it becomes interesting: annotating any type use!

The syntactic locations where annotations may appear are split into declaration contexts , where annotations apply to declarations, and type contexts, where annotations apply to types used in declarations and expressions.

Such annotations are not retained at runtime, but can be used at compile-time for a variety of "checks". See checker framework, which is built on top of work done for JSR-308 (by same author if I understand correctly).

Very quickly because it's fun, now we can do this:

@NonNull Object @Nullable [] array; // Nullable array of non-null objects
@Nullable Object @NonNull [] array; // Non-null array of nullable objects

@Foo List<@Foo Integer> doSomething(@Foo Integer @Foo [] arrayOfIntegers, @Foo long x) {
    arrayOfIntegers[0] = (@Foo int) x;
    return Arrays.asList(arrayOfIntegers);
}

Examples of such "type annotations":

The Checker Framework provides a few Type Annotations that could benefit both library and application developers, such as:
@NonNull – The compiler can determine cases where a code path might receive a null value, without ever having to debug a NullPointerException.
@ReadOnly – The compiler will flag any attempt to change the object. This is similar to Collections.unmodifiableList, but more general and verified at compile time.
@Regex – Provides compile-time verification that a String intended to be used as a regular expression is a properly formatted regular expression.
@Tainted and @Untainted – Identity types of data that should not be used together, such as remote user input being used in system commands, or sensitive information in log streams.
@m – Units of measure ensures that numbers used for measuring objects are used and compared correctly, or have undergone the proper unit conversion.

But none of those if specially useful in the context of a try-with-resources statement (I mean, no more or less than anywhere else).


Back to the question: are there uses for annotations on local variables that would be particularly interesting when declared within a try-with-resources statement?

I think in this case applications would essentially be limited to compile-time checks, because such an annotation will be either on the local variable, or on the type use, and neither is available at runtime (or not really):

  • according to the JLS annotations on local variables are not retained in the binary representation
  • annotations on type uses are written to the class file, but still not available at runtime by reflection (you'd need to parse the class file yourself!)

So, I can think of one "special" use, but I'm not even sure that would be very useful as there are probably other ways to accomplish this: for some particular types of resources that you declare in a try-with-resources statement, you might need to make sure the resource is entirely consumed before it gets closed (I've seen something like that with an HTTP client library and the part of the API that reads headers -- can't remember the details).

/* Say getResponse() taps into a third-party library that has a quirk:
 * a response object must be consumed entirely before being closed. */
try(@MustConsumeEntirely Stream<String> lines = getResponse()) {
    lines.findFirst().ifPresent(System.out::println);
    /* The stream is not entirely consumed (unless it is only 1 line but there is no way to tell).
     * A smart checker could catch this and issue a warning. */
}

This annotation would have target ElementType.LOCAL_VARIABLE (so would not require new Java 8 annotation types, but would require java 8 to be processeable), and the checker should probably verify that the variable is effectively declared within a try-with-resources statement (compiler cannot prevent from using it on any local variable), and then analyse the source tree to determine if the resources is consumed as required.
It would be probably impossible to implement such a checker in a 100% correct way, but on paper it looks possible to check for some known-bad patterns, and it would mostly make sense when the target variable is declared in a try-with-resources statement.

Another idea (still on variable and not type use), also very low usefulness: @MustNotEscape, if you want to control that the variable is not passed to another method because (for reasons similar to the above) you want the ability to control everything that happens to the object behind (e.g. as in previous idea), and that would be more difficult to accomplish if the variable is passed around.

To illustrate that such a thing is vaguely possible, here is an example of a framework that expects you to follow their "embedded DSL" inside a certain block, and fails if you don't. One could imagine an annotation to help check compliance with similar constraints imposed by an hypothetical framework on a resource within a try-with-resources block.
Not saying this would be a good design though... (I the case of ModelMapper, the DSL was only a clever trick they came up with before java 8, and they now have better & safer solutions with lambdas)

like image 171
Hugues M. Avatar answered Nov 10 '22 11:11

Hugues M.