Search code examples
javasonarqube

When does Sonar's TryStatementTree#resourceList return a Tree that isn't a VariableTree?


I came across the org.sonar.plugins.java.api.tree.TryStatementTree interface while going over some code for a SonarQube plugin. One of the rules implemented goes over the variables defined in a try-with-resources block.

Looking at the TryStatementTree#resourceList, I can see that it returns a ListTree<Tree>. This can be iterated over in a number of ways. In one case, the plugin inspects the name of the variable declared. When this happens, the Tree is cast to VariableTree because Tree is a more generic interface that doesn't provide access to variable names via IdentifierTree.

This is where the cast is in the code I'm looking at, a class that extends BaseTreeVisitor and implements JavaFileScanner.

@Override
public void visitTryStatement(TryStatementTree tree) {
  // ...
  tree.resourceList().stream()
     .filter(resource -> resource instanceof VariableTree) // Is it possible for this condition to evaluate to false? 
     .map(VariableTree.class::cast)
     .map(resource -> resource.simpleName().name())
     .forEach(SomeClass::handleThisCase);
  // ...
}

Looking at the Java Language Standard, I can't think of a situation when a try statement would have something in place of the resource declaration that isn't a list of identifiers.

I thought this had to do with the need to represent a non-present resource definitions or something similar so I threw a few unit test cases at it.

try { // No resource declaration here
  // ...
} catch (SomeException ex) {
  // ...
}

but in this case the method isn't called at all, which I guess is handled by BaseTreeVisitor.

I've been trying to come up with examples that would make the cast impossible but everything I've come up with either doesn't compile or never follows this execution path.

Am I missing a way to write a try statement that makes the more generic Tree the better choice? Or does this choice stem from the way the interfaces in the library have been structured? It doesn't seem to be enforced by any of the super-interfaces (TryStatementTree -> StatementTree -> Tree). resourceList is defined by TryStatementTree itself.


Solution

  • Ironically, I stumbled upon the answer in the SonarQube documentation minutes after posting the question.

    It turns out there's a method that works exactly as I expected it to, TryStatementTree#resource, which was deprecated and then removed in favour of TryStatementTree#resourceList, which is exactly what my code uses.

    Deprecated method org.sonar.plugins.java.api.tree.TryStatementTree.resources() has been removed, in favor of org.sonar.plugins.java.api.tree.TryStatementTree.resourceList(), as Java 9 allows other trees than VariableTree to be placed as resources in try-with-resources statements.

    Here's an example:

    SomeAutoCloseableClass myResource = obtainOneSomehow();
    try (myResurce) { // No resource declaration here, just an identifier
      // ...
    } catch (SomeException ex) {
      // ...
    }
    

    My project is compiled with the source level set to Java 8 compatibility, which should be amended and a new unit test case should be added to make sure the newly possible way to write a try-with-resources block is handled.