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

3

6

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.)

Plumley answered 26/9, 2022 at 20:11 Comment(5)
Don't you need indention inside the try to find out the current level to calculate the next one?Octameter
You can try @SuppressWarnings("try")Bow
"Don't you need indention inside the try to find out the current level to calculate the next one?" @Mihe, no, these are internal state variables inside the serializer class itself. The serializer is not thread-safe; it keeps track of its current state. I don't want to pass all the different state variables around to the methods.Plumley
@Mihe, unfortunately Eclipse claims it doesn't support @SuppressWarnings("try"), although that -Xlint option is listed in the javac docs. (Sorry, my other comment was in reply to @cyberbrain.)Plumley
This isn't limited to java 17. I'm seeing the same issue in java8 which lead me here. This might be a duplicate of #16589343Prefigure
A
0

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");
        });
    }
}
Aarika answered 26/9, 2022 at 20:42 Comment(1)
I think the gist of my question was how I could get rid of these warnings without abandoning this try-with-resources technique, but I appreciate your valid suggestion of delegating some operation function to some withIndent() code which will supply the try/finally boilerplate. On the other than, that merely pushes the try/finally boilerplate down a level, although it does consolidate it.Plumley
P
0

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.

Plumley answered 27/9, 2022 at 2:4 Comment(4)
Doing it with an annotation would be better. You only want to suppress the warning in the specific cases where you are using your clever pattern ... not everywhere. Of course, the other solution is to stop trying to be clever :-)Hydrotropism
"Doing it with an annotation would be better." Except that this pattern occurs several places in two files, and putting this annotation everywhere I use the pattern defeats part of its very purpose (conciseness). Additionally, Eclipse doesn't seem to like it, so ironically I get inline warnings because of the @SuppressWarning("try"). "Of course, the other solution is to stop trying to be clever …" I have considered that in other contexts. 😅 But where is the joy in programming if I can't create something elegant?Plumley
The API we use "to get something done" is not necessarily the same reference as the resource we opened. Ex. try (var conn = client.openPersistentConn()) { client.send(req1); client.send(req2); }. The existence of a resource may be its only purpose. Ex. try (var lock = acquireJvmReadLock(path); var ch = FileChannel.open(path)) { ch.read(...); }. Elegant ❤️(seriously, the compiler warning has no relevance to the motive of try-with-resources, and may even be misguiding?)Profant
Erm, I understand that the method "acquireJvmReadLock" may be a confusing example. The FileLock from FileChannel.tryLock can not be shared within the same JVM; only useful for inter-process co-ordination. So, within the JVM one would have to use a Lock of some kind (the example used a custom implementation that imposes structure).Profant
C
0

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.

Ctesiphon answered 13/5, 2023 at 17:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.