package com.redhat.qe.auto.instantiate;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
public class VersionedInstantiator {
protected static Logger log = Logger.getLogger(VersionedInstantiator.class.getName());
protected LinkedHashMap<String,String> packageMap = new LinkedHashMap<String,String>();
protected Integer versionedPackageIndex = 1;
protected String runningVersion = "";
protected static Map<Class<?>,Object> instances = new HashMap<Class<?>,Object>();
/**
* @param packageMap - a map of version strings to package names. For example, a product version 2.2.0 might have
* a package com.xyz.product22.stuff. You'd add an entry ("2.2.0", "product22") to the map. You should use add entries
* in increasing order (newer versions go last).
* @param versionedPackageIndex - the index of where the versioned part of the package name is. It's a 0 based index,
* so for package w.x.y.z.product22.a.b, the index is 4.
* @param runningVersion - a valid key for the packageMap that indicates the currently running version. This might
* be detected at runtime from the product itself, or read in from a properties file. For example "2.2.0".
*/
public VersionedInstantiator(LinkedHashMap<String,String> packageMap, Integer versionedPackageIndex, String runningVersion){
this.packageMap = packageMap;
this.versionedPackageIndex = versionedPackageIndex;
this.runningVersion = runningVersion;
}
public void setPackageMap(LinkedHashMap<String, String> packageMap){
this.packageMap = packageMap;
}
/**
* Takes a baseclass and returns an instance of either it or a valid subclass. The product version
* of the instance will either be the currently running version, or the next newest product version for
* which a class exists. If no other classes exist, it'll just return an instance of the base class.
* @param baseClass
* @return
*/
@SuppressWarnings("unchecked")
public Object getVersionedInstance(Class baseClass) {
return getVersionedInstance(baseClass, new Object[] {});
}
public Object getVersionedInstance(Class baseClass, Object... args) {
log.finer("Product version currently running is '" + runningVersion + "'.");
String runningPkg = packageMap.get(runningVersion);
if (runningPkg == null) {
throw new RuntimeException("The product version " + runningVersion + " is not mapped to a source package." +
" The instantiator needs to be created with a complete map of version numbers to package names.");
}
List<String> validVersions = getValidVersionList(runningPkg);
Iterator<String> it = validVersions.iterator();
Object o = null;
Class clazz = null;
while (it.hasNext() && o==null) {
String ver = it.next();
try {
String className = getClassName(baseClass, ver);
log.finer("Trying to instantiate: " + className);
clazz = Class.forName(className);
if (args.length == 0) {
//see if we already have a no-arg instance
o = instances.get(clazz);
//if not, create a new one
if (o == null) o = clazz.newInstance();
}
else {
//create a new instance with the args
o = newInstance(clazz, args);
}
}
catch(ClassNotFoundException cnfe){
log.log(Level.FINEST, "Couldn't instantiate: " + ver, cnfe);
continue;
}
catch(IllegalAccessException iae){
log.log(Level.FINEST, "Couldn't instantiate: " + ver, iae);
continue;
}
catch(InstantiationException ie){
log.log(Level.FINEST, "Couldn't instantiate: " + ver, ie);
continue;
}
}
if (o==null)throw new RuntimeException("Couldn't find any valid instance of " + baseClass.getName());
if (baseClass.isAssignableFrom(o.getClass())) {
instances.put(clazz, o);
return o;
}
else throw new RuntimeException ("The versioned class of " + baseClass.getName() + ", '" + o.getClass().getName() + "', don't have a parent/subclass relationship.");
}
protected Object newInstance(Class clazz, Object... args) {
Constructor[] constrs = clazz.getConstructors();
for (int i=0; i<constrs.length; i++) {
if (argsMatch(constrs[i], args)) {
try {
return constrs[i].newInstance(args);
}
catch(InvocationTargetException ite) {
log.log(Level.FINEST, "Could not create new instance with constructor " + constrs[i] + " and args " + args);
continue;
}
catch(IllegalAccessException ite) {
log.log(Level.FINEST, "Could not create new instance with constructor " + constrs[i] + " and args " + args);
continue;
}
catch(InstantiationException ite) {
log.log(Level.FINEST, "Could not create new instance with constructor " + constrs[i] + " and args " + args);
continue;
}
}
}
throw new RuntimeException("Could not instantiate " + clazz.getName() + " with the given arguments.");
}
protected boolean argsMatch(Constructor constr, Object... args) {
Class[] c_args = constr.getParameterTypes();
if (args.length != c_args.length) return false;
for (int i=0; i<args.length;i++) {
if (! c_args[i].isInstance(args[i])) return false;
}
return true;
}
protected String getClassName(Class<Object> baseClass, String versionedPackage){
String[] names = baseClass.getName().split("\\.");
names[versionedPackageIndex]= versionedPackage;
String name = "";
for(int i=0; i<names.length; i++){
name = name + names[i] + (i==names.length-1 ? "":".");
}
return name;
}
protected List<String> getValidVersionList(String runningPackage){
List<String> list = new ArrayList<String>(packageMap.values());
List<String> newList = new ArrayList<String>();
int i =0;
try {
while(!list.get(i).equals(runningPackage)) i++;
}
catch(IndexOutOfBoundsException ioobe){
throw new RuntimeException("The running product version '" + runningPackage + "' was not found in the map!", ioobe);
}
newList = list.subList(0, i+1);
Collections.reverse(newList);
return newList;
}
/**
* @param args
*/
public static void main(String[] args) {
}
}