6

I have an Enum called Plugins:

public enum Plugins {

    ROTATING_LINE (plugin.rotatingline.RotatingLine.class),
    SNOW_SYSTEM (plugin.snow.SnowSystem.class);

    private Class<?> c;

    private Plugins (Class<?> c) {
        this.c = c;
    }

    public Class<?> getClassObject() {
        return c;
    }

}

What I would like to do is to loop through all the enums in Plugins and create new objects from those using the variable c like this:

for (Plugins plugins : Plugins.values()) {
    Class<?> c = plugins.getClassObject();
    pluginList.add(new c(400, 400));
}

Is there a way of accomplishing this with a similar method? The reason why I want to do this is to create a list of classes that should be added to the List plugins when I start my application.

cdeszaq
  • 30,869
  • 25
  • 117
  • 173
jnsstnbrg
  • 63
  • 1
  • 1
  • 6

8 Answers8

12

Since you are iterating over the plugins, I guess all plugins' constructors have the same number & type of parameters. (I do not understand your plugins.add(...) though.)

Anyways, if you want to use Enums, I would do the following, using constant-specific method implementations instead of type tokens/reflection:

public enum PluginTypes {
   ROTATING_LINE { Plugin create(int x, int y){
         return new plugin.rotatingline.RotatingLine(x,y);} },
   SNOW_SYSTEM { Plugin create(int x, int y){
         return new plugin.snow.SnowSystem(x,y);} };

   abstract Plugin create(int x, int y);
}

public interface Plugin {
  //...
}

Then your loop would look something like this:

List<Plugin> plugins = new ArrayList<Plugin>();

for (PluginTypes pt : PluginTypes.values()) {
    plugins.add(pt.create(400, 400));
}

On the other hand, if you know your implementation classes of Plugin anyways, why not use them directly instead of via the PluginTypes Enum?

DaveFar
  • 7,078
  • 4
  • 50
  • 90
  • This is the answer I went for, seemed best for my purpose. Thanks though for all the other answers, both learned a lot and you gave me lots of inspiration! – jnsstnbrg Mar 22 '12 at 18:34
  • Glad I could help. You can indicate this by accepting the answer ;) And if you change your mind later on, you can undo/switch the answer you find best. – DaveFar Mar 22 '12 at 18:42
  • I'm new here, didn't see that option before. Done now, I hope(?)! Thank you again. – jnsstnbrg Mar 22 '12 at 19:28
3

Create an interface for the plugins

public interface Plugin{
     setShapeType();
     setX();
     setY();
     ...
  }

Create a method on the enum that creates an instance of Plugin.

public enum Plugins {

        ROTATING_LINE (plugin.rotatingline.RotatingLine.class),
        SNOW_SYSTEM (plugin.snow.SnowSystem.class);

        private Class<? extends Plugin> c;

        private Plugins (Class<? extends Plugin> c) {
            this.c = c;
        }

        // This acts as a constructor that takes args, I am assuming the args you need 
        public Class<? extends Plugin> createInstance(String shapeType,int x, int y) {
            Plugin plugin = c.newInstance();
            plugin.setShapeType(shapeType);
            plugin.setX(x);
            plugin.setY(y);
            return plugin;
        }

    }

Instantiate in loop

List<Plugin> myPlugins = new ArrayList<Plugin>();

for (Plugins plugins : Plugins.values()) {
    myPlugins.add(plugin.createInstance(Shaped.parent, 400, 400));
}

Please note this is Psuedo Code

Kevin Bowersox
  • 93,289
  • 19
  • 159
  • 189
2

First, you need all the classes to implement some kind of interface:

public interface Plugin {
    public doSomething();
}

Then you can do

Class clazz = getClassObject();
Plugin plugin = clazz.newInstance();
... add to a list where you can use later ...

About loading your plugins, you can either declare all of them in an enum... or you can declare them in a configuration .xml or .properties (for instance) that will give you more flexibility:

public void loadPlugins(){
    List<Plugin> plugins = new ArrayList<Plugin>();    
    ... load plugin names from config file ...
    for(String pluginClass : pluginsConfigList)
       plugins.add(Class.forName(pluginClass).newInstance());
}

Of course, you'll need some basic exception handling, etc - this code's been written in a hurry from what I remember doing (:

Marcelo
  • 4,580
  • 7
  • 29
  • 46
  • 1
    +1 for this, alternatively you can use the [ServiceLoader mechanism](http://docs.oracle.com/javase/6/docs/api/java/util/ServiceLoader.html) – Paaske Mar 22 '12 at 15:58
  • 3
    Apparently the OP wants to use a nondefault constructor, so `newInstance` won't work directly. – Péter Török Mar 22 '12 at 16:00
2

Enums in this situtation will make your life more difficult.

In the past I've solved this problem this way:

class PluginFactory<T extends Plugin> {

        private Class<T> clazz;

        PluginFactory(Class<T> clazz) {
            this.clazz = clazz;
        }

        T newInstance() {
            return clazz.newInstance();
        }

    final static PluginFactory<SnowSystem> SNOW_SYSTEM_FACTORY = 
            new PluginFactory(SnowSystem.class);
    ...

    // should really use a list and a wilcard generic bounded by Plugin, but it's
    // too verbose for me for the purposes of this answer
    final static PluginFactory[] FACTORIES = {SNOW_SYSTEM_FACTORY, ...};

    public static void main(String[] args) {
        Plugin[] arr = new Plugin[FACTORIES.length];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = FACTORIES[i].newInstance();
        }

        // more usefully though
        SnowSystem ss = SNOW_SYSTEM_FACTORY.newInstance();
        ss.setValue(123);
    }

}

The other option is to give newInstance var args object parameter. Then use reflections to find the appropriate constructor that takes these types as parameters. This is hideously messy and completely unsafe if a user of the API gives a bad set of arguments (exception thrown).

public T newInstance(Object... args) {    
    for (Constructor c : clazz.getConstructors()) {
        if (isMatchingConstructor(c, args)) {
            return clazz.cast(c.newInstance(args));
        }
    }
    throw new NoSuchMethodException("No matching constructor found for args: " 
            + Arrays.toString(args));
}

private boolean isMatchingConstructor(Constructor c, Object... args) {
    Class<?>[] parameters = c.getParameterTypes();

    if (parameters.length != args.length) {
        return false;
    }

    for (int i = 0; i < args.length; i++) {
        if (!parameters[i].isAssignableFrom(args[i].getClass())) {
            return false;
        }
    }
    return true;
}
Dunes
  • 37,291
  • 7
  • 81
  • 97
  • Nice use of Factories (+1). Do you happen to know the current performance penalty of reflective method lookups (with JITing and without)? I often see quite different measurements, e.g. www.jguru.com/faq/view.jsp?EID=246569 – DaveFar Mar 22 '12 at 17:19
  • The penalty comes from having to do the initial look up of the method/constructor/field eg `clazz.getConstructors()`. Overhead of actually invoking a method/constructor via reflections is minimal. So if you can cache the returned Methods/Constructors/Fields then the performance hit is negligible. – Dunes Mar 22 '12 at 17:30
  • 1
    The post you linked to is ancient (it's from the time of java 1.3!). Have a read of this http://stackoverflow.com/questions/414801/any-way-to-further-optimize-java-reflective-method-invocation – Dunes Mar 22 '12 at 17:39
  • Yup, that's why I asked ;) Do you know whether the compiler/JVM can do this caching as optimization? Or do you have to implement it by hand? – DaveFar Mar 22 '12 at 17:40
  • The comments in the post I linked to repeat the performance measurements, up to beginning of this year :) – DaveFar Mar 22 '12 at 17:41
  • your so-link is quite interesting, thanks. I wonder if thinks have changes in Java 1.7 with invokeDynamic. – DaveFar Mar 22 '12 at 17:44
  • I'm not sure about caching. On OpenJDK I tested getting and invoking Object.toString a million times. 1300ms for getting the method (only), 580ms for invoking the method normally, 660ms for invoking the method with a cached copy of `Method` (reflections). No idea how the sun/oracle JVM would perform (or java 7 for that matter). Definitely not a order of magnitude of difference, even if you didn't cache the method. – Dunes Mar 22 '12 at 18:31
1

Yes, you can do it using reflection. In fact, we are doing almost exactly the same within an enum in our project. It works nicely, although I am not 100% comfortable with all the direct dependencies this solution creates. It may be better to somehow use DI via e.g. Spring, however this hasn't bugged me enough to actually experiment with a solution.

If the class has a default constructor, simply call c.newInstance(). If not, the issue is a bit more complicated - here is another post dealing with this.

Of course, once you have the objects, you need to do something with them. The standard way is to have all classes involved implement the same interface, then you can cast down the objects to that interface and call interface methods on them. (Of course, in production code, you need to catch and handle all possible runtime exceptions which might arise - such as InstantiationException, IllegalAccessException and ClassCastException).

Community
  • 1
  • 1
Péter Török
  • 114,404
  • 31
  • 268
  • 329
0

There are 2 ways:

  1. use Enum.valueOf() static function, then cast it into your enum type.

    Enum v = Enum.valueOf(TheEnumClass, valueInString);
    
  2. Use class.getEnumConstants() function to get the list of the enum constants, and loop this list and get.

    Plugins[] plugins = Plugins.class.getEnumConstants();
    for (Plugins plugin: plugins) {
        // use plugin.name() to get name and compare
    }
    
Mavlarn
  • 3,807
  • 2
  • 37
  • 57
0

There is a conflict in your second code snippet which confuses me a bit... the variable plugins is used both for enum constants and as a list it appears. So I am assuming this, but you should post code snippets which actually work in future.

So, yes, there is a way to do what you want:

for (Plugins plugins : Plugins.values()) {
    Class<?> c = plugins.getClassObject();
    pluginsList.add(c.getConstructor(Integer.TYPE, Integer.TYPE).newInstance(400, 400));
}

Also I recommend you looking at Service Loader, which is a really cool tool to dynamically load services from the classpath.

Michael Schmeißer
  • 3,407
  • 1
  • 19
  • 32
  • Yes, in the haste of pasting code I renamed it to make it more clear, I stumbled and named some variabels really bad. Sorry. – jnsstnbrg Mar 22 '12 at 18:36
0

For creating instance of the class you can follow answer from @Peter and for holding the reference to the that object I suggest EnumMap.

EnumMap<Plugins, Object> map = new EnumMap<Plugins, Object>(Plugins.class);
for (Plugins plugins : Plugins.values()) {
    Class<?> c = plugins.getClassObject();
    map.put(plugins, c.newInstance());
}
Jaydeep Patel
  • 2,394
  • 1
  • 21
  • 27