package edu.stanford.nlp.util;
import java.lang.reflect.Constructor;
/**
A meta class using Java's reflection library. Can be used to create a single
instance, or a factory, where each Class from the factory share their
constructor parameters.
@author Gabor Angeli
*/
public class MetaClass {
public static class ClassCreationException extends RuntimeException {
private static final long serialVersionUID = -5980065992461870357L;
private ClassCreationException() {
super();
}
private ClassCreationException(String msg) {
super(msg);
}
private ClassCreationException(Throwable cause) {
super(cause);
}
}
public static final class ConstructorNotFoundException extends ClassCreationException {
private static final long serialVersionUID = -5980065992461870357L;
private ConstructorNotFoundException() {
super();
}
private ConstructorNotFoundException(String msg) {
super(msg);
}
private ConstructorNotFoundException(Throwable cause) {
super(cause);
}
}
public static final class ClassFactory<Type> {
private Class<?>[] classParams;
private Class<Type> cl;
private Constructor<Type> constructor;
private boolean samePrimitive(Class<?> a, Class<?> b){
if(!a.isPrimitive() && !b.isPrimitive()) return false;
if(a.isPrimitive()){
try {
Class<?> type = (Class<?>) b.getField("TYPE").get(null);
return type.equals(a);
} catch (Exception e) {
return false;
}
}
if(b.isPrimitive()){
try {
Class<?> type = (Class<?>) a.getField("TYPE").get(null);
return type.equals(b);
} catch (Exception e) {
return false;
}
}
throw new IllegalStateException("Impossible case");
}
private int superDistance(Class<?> candidate, Class<?> target) {
if (candidate == null) {
// --base case: does not implement
return Integer.MIN_VALUE;
} else if (candidate.equals(target)) {
// --base case: exact match
return 0;
} else if(samePrimitive(candidate, target)){
// --base case: primitive and wrapper
return 0;
} else {
// --recursive case: try superclasses
// case: direct superclass
Class<?> directSuper = candidate.getSuperclass();
int superDist = superDistance(directSuper, target);
if (superDist >= 0)
return superDist + 1; // case: superclass distance
// case: implementing interfaces
Class<?>[] interfaces = candidate.getInterfaces();
int minDist = Integer.MAX_VALUE;
for (Class<?> i : interfaces) {
superDist = superDistance(i, target);
if (superDist >= 0) {
minDist = Math.min(minDist, superDist);
}
}
if (minDist != Integer.MAX_VALUE)
return minDist + 1; // case: interface distance
else
return -1; // case: failure
}
}
@SuppressWarnings("unchecked")
private void construct(String classname, Class<?>... params)
throws ClassNotFoundException, NoSuchMethodException {
// (save class parameters)
this.classParams = params;
// (create class)
try {
this.cl = (Class<Type>) Class.forName(classname);
} catch (ClassCastException e) {
throw new ClassCreationException("Class " + classname
+ " could not be cast to the correct type");
}
// --Find Constructor
// (get constructors)
Constructor<?>[] constructors = cl.getDeclaredConstructors();
Constructor<?>[] potentials = new Constructor<?>[constructors.length];
Class<?>[][] constructorParams = new Class<?>[constructors.length][];
int[] distances = new int[constructors.length]; //distance from base class
// (filter: length)
for (int i = 0; i < constructors.length; i++) {
constructorParams[i] = constructors[i].getParameterTypes();
if (params.length == constructorParams[i].length) { // length is good
potentials[i] = constructors[i];
distances[i] = 0;
} else { // length is bad
potentials[i] = null;
distances[i] = -1;
}
}
// (filter:type)
for (int paramIndex = 0; paramIndex < params.length; paramIndex++) { // for
// each
// parameter...
Class<?> target = params[paramIndex];
for (int conIndex = 0; conIndex < potentials.length; conIndex++) { // for
// each
// constructor...
if (potentials[conIndex] != null) { // if the constructor is
// in the pool...
Class<?> cand = constructorParams[conIndex][paramIndex];
int dist = superDistance(target, cand);
if (dist >= 0) { // and if the constructor matches...
distances[conIndex] += dist; // keep it
} else {
potentials[conIndex] = null; // else, remove it from
// the pool
distances[conIndex] = -1;
}
}
}
}
// (filter:min)
this.constructor = (Constructor<Type>) argmin(potentials, distances, 0);
if (this.constructor == null) {
StringBuilder b = new StringBuilder();
b.append(classname).append("(");
for (Class<?> c : params) {
b.append(c.getName()).append(", ");
}
String target = b.substring(0, params.length==0 ? b.length() : b.length() - 2) + ")";
throw new ConstructorNotFoundException(
"No constructor found to match: " + target);
}
}
private ClassFactory(String classname, Class<?>... params)
throws ClassNotFoundException, NoSuchMethodException {
// (generic construct)
construct(classname, params);
}
private ClassFactory(String classname, Object... params)
throws ClassNotFoundException, NoSuchMethodException {
// (convert class parameters)
Class<?>[] classParams = new Class[params.length];
for (int i = 0; i < params.length; i++) {
if(params[i] == null) throw new ClassCreationException("Argument " + i + " to class constructor is null");
classParams[i] = params[i].getClass();
}
// (generic construct)
construct(classname, classParams);
}
private ClassFactory(String classname, String... params)
throws ClassNotFoundException, NoSuchMethodException {
// (convert class parameters)
Class<?>[] classParams = new Class[params.length];
for (int i = 0; i < params.length; i++) {
classParams[i] = Class.forName(params[i]);
}
// (generic construct)
construct(classname, classParams);
}
/**
* Creates an instance of the class produced in this factory
*
* @param params
* The arguments to the constructor of the class NOTE: the
* resulting instance will [unlike java] invoke the most
* narrow constructor rather than the one which matches the
* signature passed to this function
* @return An instance of the class
*/
public Type createInstance(Object... params) {
try {
boolean accessible = true;
if(!constructor.isAccessible()){
accessible = false;
constructor.setAccessible(true);
}
Type rtn = constructor.newInstance(params);
if(!accessible){ constructor.setAccessible(false); }
return rtn;
} catch (Exception e) {
throw new ClassCreationException(e);
}
}
/**
* Returns the full class name for the objects being produced
*
* @return The class name for the objects produced
*/
public String getName() {
return cl.getName();
}
@Override
public String toString() {
StringBuilder b = new StringBuilder();
b.append(cl.getName()).append("(");
for (Class<?> cl : classParams) {
b.append(" ").append(cl.getName()).append(",");
}
b.replace(b.length() - 1, b.length(), " ");
b.append(")");
return b.toString();
}
@SuppressWarnings("unchecked")
@Override
public boolean equals(Object o) {
if (o instanceof ClassFactory) {
ClassFactory other = (ClassFactory) o;
if (!this.cl.equals(other.cl))
return false;
for (int i = 0; i < classParams.length; i++) {
if (!this.classParams[i].equals(other.classParams[i]))
return false;
}
return true;
} else {
return false;
}
}
@Override
public int hashCode() {
return cl.hashCode();
}
}
private String classname;
/**
* Creates a new MetaClass producing objects of the given type
*
* @param classname
* The full classname of the objects to create
*/
public MetaClass(String classname) {
this.classname = classname;
}
/**
* Creates a new MetaClass producing objects of the given type
*
* @param classname
* The class to create
*/
public MetaClass(Class<?> classname) {
this.classname = classname.getName();
}
/**
* Creates a factory for producing instances of this class from a
* constructor taking the given types as arguments
*
* @param <E>
* The type of the objects to be produced
* @param classes
* The types used in the constructor
* @return A ClassFactory of the given type
*/
public <E> ClassFactory<E> createFactory(Class<?>... classes) {
try {
return new ClassFactory<E>(classname, classes);
} catch (ClassCreationException e){
throw e;
} catch (Exception e) {
throw new ClassCreationException(e);
}
}
/**
* Creates a factory for producing instances of this class from a
* constructor taking the given types as arguments
*
* @param <E>
* The type of the objects to be produced
* @param classes
* The types used in the constructor
* @return A ClassFactory of the given type
*/
public <E> ClassFactory<E> createFactory(String... classes) {
try {
return new ClassFactory<E>(classname, classes);
} catch (ClassCreationException e){
throw e;
} catch (Exception e) {
throw new ClassCreationException(e);
}
}
/**
* Creates a factory for producing instances of this class from a
* constructor taking objects of the types given
*
* @param <E>
* The type of the objects to be produced
* @param objects
* Instances of the types used in the constructor
* @return A ClassFactory of the given type
*/
public <E> ClassFactory<E> createFactory(Object... objects) {
try {
return new ClassFactory<E>(classname, objects);
} catch (ClassCreationException e){
throw e;
} catch (Exception e) {
throw new ClassCreationException(e);
}
}
/**
* Create an instance of the class, inferring the type automatically, and
* given an array of objects as constructor parameters NOTE: the resulting
* instance will [unlike java] invoke the most narrow constructor rather
* than the one which matches the signature passed to this function
*
* @param <E>
* The type of the object returned
* @param objects
* The arguments to the constructor of the class
* @return An instance of the class
*/
public <E> E createInstance(Object... objects) {
ClassFactory<E> fact = createFactory(objects);
return fact.createInstance(objects);
}
/**
* Creates an instance of the class, forcing a cast to a certain type and
* given an array of objects as constructor parameters NOTE: the resulting
* instance will [unlike java] invoke the most narrow constructor rather
* than the one which matches the signature passed to this function
*
* @param <E>
* The type of the object returned
* @param type
* The class of the object returned
* @param params
* The arguments to the constructor of the class
* @return An instance of the class
*/
@SuppressWarnings("unchecked")
public <E,F extends E> F createInstance(Class<E> type, Object... params) {
Object obj = createInstance(params);
if (type.isInstance(obj)) {
return (F) obj;
} else {
throw new ClassCreationException("Cannot cast " + classname
+ " into " + type.getName());
}
}
public boolean checkConstructor(Object... params){
try{
createInstance(params);
return true;
} catch(ConstructorNotFoundException e){
return false;
}
}
@Override
public String toString() {
return classname;
}
@Override
public boolean equals(Object o) {
if (o instanceof MetaClass) {
return ((MetaClass) o).classname.equals(this.classname);
}
return false;
}
@Override
public int hashCode() {
return classname.hashCode();
}
/**
* Creates a new MetaClass (helper method)
*
* @param classname
* The name of the class to create
* @return A new MetaClass object of the given class
*/
public static MetaClass create(String classname) {
return new MetaClass(classname);
}
/**
* Creates a new MetaClass (helper method)
*
* @param clazz
* The class to create
* @return A new MetaClass object of the given class
*/
public static MetaClass create(Class <?> clazz) {
return new MetaClass(clazz);
}
private static final <E> E argmin(E[] elems, int[] scores, int atLeast){
int argmin = argmin(scores, atLeast);
return argmin >= 0 ? elems[argmin] : null;
}
private static final int argmin(int[] scores, int atLeast){
int min = Integer.MAX_VALUE;
int argmin = -1;
for(int i=0; i<scores.length; i++){
if(scores[i] < min && scores[i] >= atLeast){
min = scores[i];
argmin = i;
}
}
return argmin;
}
}