walkeros
walkeros

Reputation: 4942

Java generic type bounded to classes with specific annotation

In java it is possible to bound the generic parameter to classes implementing specific interface, so following is possible

interface MyInterface {}

class MyClassA implements MyInterface {}

class MyBoundedClassA<T extends MyInterface>

Now what if instead the interface I would like to bound the parameter to class annotated with specific annotation, like:

interface @MyAnnotation {}

@MyAnnotation
class MyClassB {}

class MyBoundedClassB<T extends MyAnnotation> // NOT possible

Is it possible to achieve such a behavoiur in Java?

---- EDIT

On request adding real world example. Slighly modifying the domain to make the example more understandable.

There is well known jackson library for serializing objects. This library does not support serialization of map keys other than Strings, so following is not possible out of the box

class TimeRange {
  LocalDateTime startDate;
  LocalDateTime endDate;

}

class SportsmenActivities {
  private Map<String, <TimeRange, List<Activity>>  sportActivities;
}

In this example the key of outer map is "sportsmanCode" liek "andy", "mike", "john" .Inner Map contains activities performed by given sportsman within given period.

So let's say Andy, was jogging for one day than the entry would be:

new SportsmanActivities().get("andy").put(TimeRange.of('2012-12-01,'2012-12-02'), List.with(new JoggingActivity)) // did some pseudo code here for readablity

Now as said Jackson will not serialize that out of the box, so I wrote generic module which allows serialization of such complex map.

To use that what you need to do is to annotate your "key" class like that:

@KeySerializable
class TimeRange {
  @MyMapKey
  LocalDateTime startDate;
  @MyMapKey
  LocalDateTime endDate;

}

As you can guess fields annotated with @MyMapKey will be used to generate MapKey.

Now I have a implementation of jackson class which dynamically serializes everything passed as a "text map key" annotated with @KeySerializable. The signature is follwing

    class MyMapKeySerializer<T> extends JsonSerializer<T> {
      serialize (T keyToSerialize) { 
      // do magic 
      }

   }

This works, but I would like to limit T to only accept classes annotated with @KeySerializable, as only for such classes this method makes sense. Ideally this would be something like:

   class MyMapKeySerializer<T annotatedWith @KeySerializable> extends JsonSerializer<T> {
      serialize (T keyToSerialize) { 
      // do magic 
      }

   } 

Upvotes: 0

Views: 2766

Answers (2)

mernst
mernst

Reputation: 8117

A tool like the Checker Framework plugs into a compiler to restrict generic instantiation in a way similar to what you requested. It is implemented as an annotation processor, and it gives a compile-time guarantee of correct use.

For example, you can write class MyList<T extends @NonNull Object> {...}.

The Checker Framework enables you to build your own checker, which enforces any rules you like about @KeySerializable. In your case, the rules might be so simple that you can just define a couple of type qualifiers and use the Subtyping Checker -- at least at first.

Note that for the Checker Framework to work using the @KeySerializable annotation, that annotation must be a type annotation rather than a declaration annotation.

Upvotes: 1

drekbour
drekbour

Reputation: 3081

If your goal is to assert only annotated classes are accepted then you have few workaround options:

  1. Write an annotation-processor that does the assertion at compile time (see how @NonNull etc work). This is is interesting work but non-trivial as the compilation/type system is totally new to many seasoned Java devs.
  2. Use some form AOP (AspectJ, Spring AOP etc) to "advise" all annotated methods with a Decorator whose responsibility is to assert the parameter has the same annotation.
  3. Explicitly check at runtime using parameter.getClass().isAnnotationPresent(MyAnnotation.class)

Upvotes: 4

Related Questions