Search code examples
javagenericsinterfacecastingclassloader

Generic Interface Casting


I am working on creating a very simplistic OS-like framework (similar to Android) built off of the JVM for educational purposes.

Mostly, I just wanted to learn about ClassLoaders and the endless possibilities of them. I have the majority of the infrastructure created, but I am questioning whether there may be a better way of handling the generics of the situation that I am in.

A little description of the way my framework operates:

"App" implementations in my framework have the ability to attach extensions, i.e. plugins. The extension system works by requiring extensions to implement an interface that the App code is aware of, but the App code has no knowledge of the extensions at all, it just knows of the interface.

This is where my use of ClassLoaders comes in. I use the URLClassLoader to load in all .class files in the $ApplicationRoot/ext directory recursively and test to see if the interface isAssignable( from the loaded classes).

This all works fine and dandy except when I try to further generalize it. What I want to do is have SDK code that will handle all of this, so that an app would just need to make sure the proper extensions are in the proper folder.

So here is my code right now:

Set<String> classFiles = new HashSet<>();
gatherAllClassFiles("", classFiles, srcRoot);
gatherAllClassFiles("", classFiles, extRoot);

URLClassLoader ucl = new URLClassLoader(new URL[]{extRoot.toURI().toURL(), srcRoot.toURI().toURL()});

for(String className:classFiles){

    Class<?> clazz = ucl.loadClass(className);
    for(String extension: extensionInterfaceNames){
        Class<?> iface = Class.forName(extension);
        if(iface.isAssignableFrom(clazz)){
            extensions.putIfAbsent(iface, new ArrayList<>());
            extensions.get(iface).add(iface.cast(clazz.newInstance()));
        }
    }
}

extensions is declared as follows:

private Map<Class<?>, List<?>> extensions;

I am aware that this implementation of generics is not in the least bit ideal, in fact, in its current state, it won't compile, but I really wanted to stay away from using List<Object>.

What I really would like to be able to do is something like this:

private Map<Class<?>, List<iface>> extensions;

given that iface is the name of the a Class variable holding the class information and not the actual Type, this will not work.

My gut feeling is that I will have to suck it up and use List<Object> and unsafely cast it out, but I was hoping that someone else might have a better suggestion.


Solution

  • A couple of ideas which didn't easily fit in a comment:

    1. What immediately comes to mind is that you could pass a Class to the extension loading routine which each of the loaded classes ought to be a subtype of.

      For example:

      public <E> Map<Class<? extends E>, List<E>> loadExtensions
         (Class<E>     extType,
          List<String> extNames) {
          // ...;
      }
      

      However, this only works if there's some place which does know statically of a class which each of the extensions should be a subtype of. It could be that extension loading would then be delegated to the app implementation. For example, they might end up doing something like:

      class CalculatorApp extends App {
          Map<Class<? extends Calculation>, List<Calculation>> extensions =
              loadExtensions(Calculation.class, ...);
      }
      
    2. You could also dump all of the extensions in to e.g. your Map<Class<?>, List<Object>> and then have them be retrievable by type:

      abstract class App {
          private final Map<Class<?>, List<Object>> extensions =
              loadExtensions();
      
          protected final <E> List<E> findExtensions(Class<E> extType) {
              return extensions.entrySet().stream()
                               .filter(e -> extType.isAssignableFrom(e.getKey()))
                               .flatMap(e -> e.getValue().stream())
                               .map(extType::cast)
                               .collect(Collectors.toList());
          }
      }
      
    3. Another way which I don't particularly like but could be worth considering is that the App carries a type parameter for the extension type:

      abstract class App<E> {
          protected final Class<E> extType;
          protected final Map<Class<? extends E>, List<E>> extensions;
      
          protected App(Class<E> extType) {
              this.extType    = extType;
              this.extensions = loadExtensions(extType);
          }
      }
      

      Then it gets implemented as:

      class CalculatorApp extends App<Calculation> {
          CalculatorApp() { super(Calculation.class); }
      }
      

      You could also probably use the getGenericSuperclass().getActualTypeArguments() idiom or TypeToken in this case, instead of passing a class.

    4. A cross between #1 and #3, you could abstract the extensions in to a separate container:

      class Extensions<E> {
          private final Class<E> type;
          private final List<E>  extensions;
      
          public Extensions(Class<E> type) {
              this.type       = type;
              this.extensions = App.findExtensions(type);
          }
      }
      

      That would allow an implementation to more easily e.g. use multiple extension types. For the calculator example, maybe they want to have both a Calculation extension and, say, a NumberDisplayFormat extension.

    Anyway, it would seem that no matter what you decide to do, somebody needs to know statically what the supertype of their extensions is.