Working around or suppressing try-with-resources varible not used warning

1.6k Views Asked by At

In Java 17 I have a serializer that generates a tree structure. Before generating child entities, I increase the indention level; afterwards I decrease the indention level. Normally that should be done in a try/finally to keep the serializer from being left in a corrupt state if there is an error:

increaseIndentLevel();
try {
  serializeChildren()
} finally {
  decreaseIndentLevel()
}

Using try-with-resources I have created a clever and elegant little subframework that makes sure this is done, in a more fluent way:

protected Closeable increaseIndentLevel() {
  indentLevel++;
  return Close.by(this::decreaseIndentLevel);
}

Close.by() is my helper class that creates a Closeable that will decrease the indent level just like I do above; I can use it like this:

try (final Closeable indention = increaseIndentLevel()) {
  serializeChildren()
}

Unfortunately OpenJDK javac 17 with linting turned on doesn't recognize my cleverness, and instead complains:

[WARNING] auto-closeable resource indention is never referenced in body of corresponding try statement

I understand that try-with-resources requires that I declare some variable. I can't say try (increaseIndentLevel()) for example. (I also can guess the reason: the creators of this feature didn't generalize enough and instead created unnecessarily restrictive rules for the obvious, 99% use case. In reality there is no need conceptually to require a variable here; if the body needs to reference something, the compiler is smart enough to notice that the referenced variable is not present.)

Any idea how to get around this warning?

As a last resort, what identifier do I use with @SuppressWarnings() to make this warning go away in javac? (I had to supress the warning, because it turns such a pretty solution into something so ugly.)

3

There are 3 best solutions below

1
tgdavies On

I'd write something like this to avoid mutability. Without seeing more of your use case it's hard to know what to suggest:

import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Indenter {
    private final int indentLevel;

    public Indenter(int indentLevel) {
        this.indentLevel = indentLevel;
    }

    public void withIndent(Consumer<Indenter> f) {
        f.accept(new Indenter(indentLevel + 1));
    }

    public void indent(String s) {
        // must be a neater way to do this?
        System.out.println(IntStream.range(0, indentLevel).mapToObj(i -> " ").collect(Collectors.joining()) + s);
    }

    public static void main(String[] args) {
        Indenter i = new Indenter(0);
        i.indent("x");
        i.withIndent(i2 -> {
            i2.indent("y");
        });
    }
}
4
Garret Wilson On

Taking a cue from @Mihe's comment to the question, one workaround is disable the try linting in the compiler itself, like this in Maven:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <configuration>
    <compilerArgs>
      <!-- suppress warnings for try-with-resources in
          serializer indentation encpsulation -->
      <arg>-Xlint:all,-try</arg>
    </compilerArgs>
  </configuration>
</plugin>

I still find that a little heavy-handed. I'm considered refactoring the code to use @tgdavies' functional approach, but I still haven't made a decision. I thought I'd provide this solution for completeness.

0
Michael Deardeuff On

What I end up doing is adding an extra (noop) helper method in my Close utilities.

try (final Closeable indention = increaseIndentLevel()) {
    Close.suppressUnused(indentation);

    serializeChildren()
}

Another alternative is to add to a close collector:

try (final CloseLine close = new CloseLine()) {
    close.with(increaseIdentation());

    serializeChildren()
}

I prefer the first. Both add two lines, one of which is empty. The first is vastly simpler in both implementation and readability, and it gets optimized away.