In my project I have the following base interfaces used through the application:
interface Node { /* ... */ }
interface NodeService<T> { /* ... */ }
Concrete classes might look as follows:
class User implements Node { /* ... */ }
class UserService implements NodeService<User> { /* ... */ }
class Entity implements Node { /* ... */ }
class EntityService implements NodeService<Entity> { /* ... */ }
In the end I want to have a Map<String, Class<? extends NodeService<Node>>>
that in the case above might look as follows:
Map{
User=UserService.class,
Entity=EntityService.class
}
My current solution has the following approach:
- Using the Reflections library find all classes implementing
NodeService
- Iterate over all generic interfaces of said classes until we find the
NodeService
interface - Access its first and single generic type argument
- Cast the type to a class and map it to its simple name
- Build a tuple (using vavr in this case) containing the simple name and the service class we found in step 1
- Flatten the result and collect the tuples into a map
@SuppressWarnings({"unchecked", "rawtypes"})
final Map<String, Class<? extends NodeService<Node>>> nodeServices =
new Reflections(BackendApplication.class.getPackageName())
.getSubTypesOf(NodeService.class).stream()
.map(
serviceClass ->
Stream.of(serviceClass.getGenericInterfaces())
.filter(ParameterizedType.class::isInstance)
.map(ParameterizedType.class::cast)
.filter(type -> type.getRawType().equals(NodeService.class))
.map(ParameterizedType::getActualTypeArguments)
.map(typeArguments -> typeArguments[0])
.filter(Class.class::isInstance)
.map(Class.class::cast)
.map(Class::getSimpleName)
.map(nodeClass -> Tuple.of(nodeClass, (Class) serviceClass))
.collect(Collectors.toSet()))
.flatMap(Collection::stream)
.collect(Collectors.toMap(Tuple2::_1, Tuple2::_2));
I am surprised that this works in the first place but the code is ridiculously complex and I am looking for suggestions on how to rewrite this. Some of my biggest pain points are the handling of the classes generic interfaces even though in the end I'm only interested in a single one of them making the stream in the middle introduce unnecessary nesting. Furthermore the way I attempt to navigate to the correct type is really bothering me as well.
Thus I am open for your input on how to improve this chunk of code.
The performance of this snippet is surprisingly good. I am mainly looking for input on the readability and refactoring this to make it much more maintainable, even at the cost of performance since this will be only invoked once during the application lifecycle.