I came up with a solution today involving creating classes at runtime, after parsing a file, using the Reflection API in Java.
while ((line = textReader.readLine()) != null)
{
Pattern p = Pattern
.compile("([^:]+):([^:]+)::([\\d]+)::([^:]+)::(.+)");
Matcher m = p.matcher(line);
if (m.find())
{
String id = m.group(1);
String className = m.group(2);
int orderOfExecution = Integer.valueOf(m.group(3));
String methodNameOrNew = m.group(4);
Object[] arguments = m.group(5).split("::");
if (methodNameOrNew.compareTo("new") == 0)
{
System.out.println("Loading class: " + className);
if (className.contains("Competition"))
{
continue;
}
else if (className.contains("$"))
{
continue;
}
else
{
Class<?> cl = Class.forName(className);
printMembers(cl.getConstructors(), "Constructor");
Constructor<?>[] cons = cl.getConstructors();
Object obj = cons[0].newInstance(arguments);
this.map.put(id, obj);
}
}
}
}
and printMembers():
private static void printMembers(Member[] mbrs, String s)
{
out.format("%s:%n", s);
for (Member mbr : mbrs)
{
if (mbr instanceof Field)
out.format(" %s%n", ((Field) mbr).toGenericString());
else if (mbr instanceof Constructor)
out.format(" %s%n", ((Constructor) mbr).toGenericString());
else if (mbr instanceof Method)
out.format(" %s%n", ((Method) mbr).toGenericString());
}
if (mbrs.length == 0)
out.format(" -- No %s --%n", s);
out.format("%n");
}
However, I get the following error:
Loading class: org.powertac.common.TariffSpecification
Constructor:
public org.powertac.common.TariffSpecification(org.powertac.common.Broker,org.powertac.common.enumerations.PowerType)
java.lang.IllegalArgumentException: argument type mismatch
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:525)
at Parser.parse(Parser.java:64)
at Parser.main(Parser.java:137)
arguments[] is : 1 : CONSUMPTION. How could I create the right constructor, and give it the right arguments (types) ?
For example, in the sample parser I'm using I have:
2233:org.powertac.common.Tariff::6::new::6
then I have to create a class of the type org.powertac.common.Tariff (new tells me a new object needs to be created, and it takes a double rate as argument, in this case 6. However, I don't know it takes a double, only the argument is String (6). How could I create / convert / cast to the correct type and then assign it to the constructor? My first thought was to create a symbol table, but I'm wondering about an easier solution...
You need to use Class.getConstructor(Class...) to choose the constructor that is appropriate for arguments you wish to pass in to Constructor.newInstance(Object...)
In your example I'm going to assume an array of 1 : CONSUMPTION means you have an array equivalent to
Object[] arguments = new Object[]{Integer.valueOf(1), "CONSUMPTION"};
So you call the following
Class clazz = ... //Whatever class reference you have
Constructor c = clazz.getConstructor(Integer.class, String.class);
Object obj = c.newInstance(arguments);
If you don't know the types of your arguments you will have to test the argument set against the Class array returned by Constructor.getParameterTypes() for each constructor returned by Class.getConstructors() until you find a constructor that matches your argument array. More specifically, the array of arguments and array of classes are the same length and each class in the class array passes Class.isAssignableFrom(Class) for the class of the value in the same position in the argument array.
Implementation of above in code
public boolean canConstruct(Object[] args, Constructor<?> c){
Class<?>[] paramTypes = c.getParameterTypes();
if(args.length != paramTypes.length){
return false;
}
int i = 0;
for(Object arg: args){
if(!paramTypes[i].isAssignableFrom(arg.getClass())){
return false;
}
i++;
}
return true;
}
In order to use this you will have to have your argument array as you want to pass it to the constructor. You could try to edit your input so that it includes type information (this is similar to how java serialization works) so that you can construct the arguments for the constructor argument array via reflection with their own type constructors
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With