ps777
ps777

Reputation: 296

How can I create custom compiler warnings in java?

I'm looking for something that's similar to implementing the java.lang.AutoCloseable interface, where a compiler warning indicating Resource leak: 'xxxx' is never closed is generated.

The use case for this is in a wrapper around a Synchronized Collection in java. The wrapper has an internal semaphore to prevent concurrent modification of the collection.

It allows atomic operations on the collection, in which case the semaphore is acquired and released internally. It also allows the lock to be acquired externally, providing a unique key with which operations can be executed on the collection. The key must be released at the end of the "transaction".

My goal is to create a compiler warning when the lock is acquired and not released within the same method, to prevent deadlock. An alternative design solution that would prevent this is also acceptable.

It's kind of a fun little problem, so I appreciate any insight into it.

Upvotes: 2

Views: 600

Answers (3)

ps777
ps777

Reputation: 296

While @Christian Hujer did provide a solid solution, I chose to go another route which has been working out well.

There is a wrapper class "Resource" around the SynchronizedCollection which contains:

  • A semaphore for locking the collection
  • A randomly generated ID representing the key to the currently held lock
  • Methods for performing atomic operations on the collection (They acquire the lock, perform the operation, and immediately release it)
  • Methods for performing non-atomic operations on the collection (They accept an ID as the key and perform the requested operation if the provided key matches the key currently holding the lock)

The class described above is enough to provide sufficient protection around the collection, but what I wanted was compiler warnings if the lock wasn't released.

To accomplish this, there is a "ResourceManager" which implements java.lang.AutoCloseable

This class:

  • Is passed a reference to the "Resource" via the constructor
  • Acquires the lock for the reference in the constructor
  • Provides an API for calling the non-atomic methods on the "Resource" using the key it acquired during construction
  • Provides a close() method, overriding java.lang.AutoCloseable, which releases the lock acquired during construction

The resource manager is created wherever multiple operations need to be performed on the Resource and a compiler warning is generated if close() is not called on any particular code path. Additionally, in java 7+, the manager can be created in a try-with-resource block and the lock is automatically released, regardless of what happens in the block.

Upvotes: 0

Christian Hujer
Christian Hujer

Reputation: 17945

As you said

An alternative design solution that would prevent this is also acceptable.

So here it is: As an alternative design solution, use Functional Programming.

Instead of finding out about the error, why not prevent the error from happening in the first place?

Lacking your source code, I make a few assumptions about your code:

  • Semaphore is your class (or interface) that provides the semaphore to your SynchronizedCollection.
  • Semaphore provides two methods obtain() and release().

The problem that you're actually facing is a problem of State resp. Change of State which leads to Temporal Coupling. obtain() and release() must be called in order. You can use elements from Functional Programming as an alternative design.

The Semaphore would currently look like this:

public class Sempahore {
    // ...
    public void obtain() {
        // Lock code
    }
    public void release() {
        // Release code
    }
}

The Semaphore user would currently look like this:

semaphore.obtain();
// Code protected by the Sempahore.
semaphore.release();

The solution is to combine obtain() and release() into a single function which takes the code to be protected as its argument. This technique is also known as Passing a Block, or more formally as a higher order function - a function that takes another function as an argument or returns another function.

Java also has function pointers, not directly, but indirectly, via references to interfaces. Since Java 8, an interface that has only one abstract method is even called Functional Interface, and Java 8 provides an optional annotation @FunctionalInterface for that.

So, your class Sempahore could instead look like this:

public class Semaphore {
    // ...
    private void obtain() {
        // Lock code
    } 
    private void release() {
        // Release code
    }
    public <V> void protect(final Callable<V> c) throws Exception {
        obtain();
        try {
            return c.call();
        } finally {
            release();
        }
    }
}

And the caller would look like this, in Java 7 and older:

semaphore.protect(new Callable<Object>() {
    public Object call() {
        // Code protected by the Semaphore.
    }
});

In Java 8 and newer, the code could also look like this:

semaphore.protect(() -> {
    // Code protected by the Semaphore.
});

Quirks about this solution

There's one aspect about Java which sucks completely in this context: Exception Handling. With functional programming, there is urgent need to fix that, but Oracle didn't. I'm still hoping for Java 9, but that won't help all that broken API like java.util.stream that's already out there in the wild. Java 8 still maintains the handle-or-declare-rule of checked exceptions, but functional programming does not take that into account nicely.

There are a few workarounds for that:

  • Use Runnable, if you do not need return values.
  • Use your own Callable interface which declares a type parameter for exceptions.

I bet using Runnable is straight-forward and self-explanatory, therefore I won't elaborate on that.

Using your own version of the Callable interface would look like this:

public interface ProtectedCode<V,E> {
    V call() throws E;
}

public class Semaphore {
    // ...
    private void obtain() {
        // Lock code
    } 
    private void release() {
        // Release code
    }
    public <V, E> void protect(final ProtectedCode<V, E> c) throws E {
        obtain();
        try {
            return c.call();
        } finally {
            release();
        }
    }
}

Now you don't need to mess around with Exception as long as the limited (because it can reflect only one type, not a type set) type inference for type parameter E leads to reasonable results in the compiler.

If you want to be extraordinarily friendly to your users, you could actually offer three variants of the protect method:

  • public void protect(final Runnable r)
  • public <V> V protect(final Callable<V> c) throws Exception
  • public <V,E> V protect(final ProtectedCode<V,E> c) throws E

Upvotes: 3

nrainer
nrainer

Reputation: 2623

In order to create compiler warnings, you will need to extend the Eclipse compiler.

An alternative solution was to create a custom check in a software quality analysis system such as Teamscale or SonarQube. The custom checks perform a static analysis of the code (usually based on the abstract syntax tree enriched with semantic information) and create issues whey they detect dodgy code. The issues are displayed on the user interface of the quality analysis system. Eclipse plugins allow an integration of the systems in Eclipse so that the issues can be listed there as well.

Upvotes: 1

Related Questions