package jadex.commons;
import jadex.commons.collection.SCollection;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.WeakHashMap;
/**
* This class provides several useful static reflection methods.
*/
public class SReflect
{
//-------- attributes --------
/** Class lookup cache (classloader(weak)->Map([name, import]->class)). */
protected static Map classcache = Collections.synchronizedMap(new WeakHashMap());
/** Inner class name lookup cache. */
protected static Map innerclassnamecache = Collections.synchronizedMap(new WeakHashMap());
/** Method lookup cache (class->(name->method[])). */
protected static Map methodcache = Collections.synchronizedMap(new WeakHashMap());
/** Field lookup cache (class->(name->field[])). */
protected static Map fieldcache = Collections.synchronizedMap(new WeakHashMap());
/** Mapping from basic class name -> basic type(class). */
protected static Map basictypes;
/** Mapping from basic class -> object type(class). */
protected static Map wrappedtypes;
static
{
basictypes = Collections.synchronizedMap(new HashMap());
basictypes.put("boolean", boolean.class);
basictypes.put("int", int.class);
basictypes.put("double", double.class);
basictypes.put("float", float.class);
basictypes.put("long", long.class);
basictypes.put("short", short.class);
basictypes.put("byte", byte.class);
basictypes.put("char", char.class);
wrappedtypes = Collections.synchronizedMap(new HashMap());
wrappedtypes.put(boolean.class, Boolean.class);
wrappedtypes.put(int.class, Integer.class);
wrappedtypes.put(double.class, Double.class);
wrappedtypes.put(float.class, Float.class);
wrappedtypes.put(long.class, Long.class);
wrappedtypes.put(short.class, Short.class);
wrappedtypes.put(byte.class, Byte.class);
wrappedtypes.put(char.class, Character.class);
}
//-------- methods --------
/**
* Get the wrapped type. This method converts
* basic types such as boolean or int to the
* object types Boolean, Integer.
* @param clazz The basic class.
* @return The wrapped type, return clazz when
* it is no basic type.
*/
public static Class getWrappedType(Class clazz)
{
assert clazz!=null;
// (jls) there are the following primitive types:
// byte, short, int, long, char, float, double, boolean
Class result = (Class)wrappedtypes.get(clazz);
return result==null ? clazz : result;
}
/**
* Is basic type.
* @return True, if the class is a basic type.
*/
public static boolean isBasicType(Class clazz)
{
return wrappedtypes.get(clazz)!=null;
}
/**
* Extension for Class.forName(), because primitive
* types are not supported.
* Uses static cache to speed up lookup.
* @param name The class name.
* @return The class, or null if not found.
*/
public static Class classForName0(String name, ClassLoader classloader)
{
return classForName0(name, true, classloader);
}
/**
* Extension for Class.forName(), because primitive
* types are not supported.
* @param name The class name.
* @return The class, or null if not found.
*/
public static Class classForName0(String name, boolean initialize, ClassLoader classloader)
{
if(name==null)
throw new IllegalArgumentException("Class name must not be null.");
Object ret = basictypes.get(name);
// System.out.println("cFN0 cache: "+clazz);
if(ret==null)
{
if(classloader==null)
classloader = SReflect.class.getClassLoader();
// For arrays get plain name and count occurrences of '['.
String clname = name;
if(clname.indexOf('[')!=-1)
{
int dimension = 0;
for(int i=clname.indexOf('['); i!=-1; i=clname.indexOf('[', i+1))
{
dimension++;
}
clname = clname.substring(0, clname.indexOf('['));
Class clazz = classForName0(clname, initialize, classloader);
if(clazz!=null)
{
// Create array class object. Hack!!! Is there a better way?
ret = Array.newInstance(clazz, new int[dimension]).getClass();
}
}
else
{
try
{
// Do not use ClassLoader.loadClass() due to Java bug #6434149
ret = Class.forName(name, initialize, classloader);
// System.out.println("cFN0: loaded "+clazz);
}
catch(ClassNotFoundException e)
{
// e.printStackTrace();
}
// Also handled by dynamic url class loader, but not in applets/webstart.
catch(LinkageError e)
{
// e.printStackTrace();
}
}
}
return (Class)ret;
}
/**
* Extension for Class.forName(), because primitive
* types are not supported.
* Uses static cache to speed up lookup.
* @param name The class name.
* @return The class.
*/
public static Class classForName(String name, ClassLoader classloader)
throws ClassNotFoundException
{
Object clazz = classForName0(name, classloader);
if(clazz==null)
{
throw new ClassNotFoundException("Class "+name+" not found.");
}
return (Class)clazz;
}
/**
* Beautifies names of arrays (eg 'String[]' instead of '[LString;').
* @return The beautified name of a class.
*/
public static String getClassName(Class clazz)
{
int dim = 0;
if(clazz==null)
throw new IllegalArgumentException("Clazz must not null.");
while(clazz.isArray())
{
dim++;
clazz = clazz.getComponentType();
}
String classname = clazz.getName();
for(int i=0; i<dim; i++)
{
classname += "[]";
}
return classname;
}
/**
* Get unqualified class name.
* Also beautifies names of arrays (eg 'String[]' instead of '[LString;').
* @return The unqualified (without package) name of a class.
*/
public static String getUnqualifiedClassName(Class clazz)
{
String classname = getClassName(clazz);
StringTokenizer stok = new StringTokenizer(classname,".");
while(stok.hasMoreTokens())
{
classname = stok.nextToken();
}
return classname;
}
/**
* Get inner class name.
* @return The inner class's name (without declaring class).
*/
public static String getInnerClassName(Class clazz)
{
String classname = (String)innerclassnamecache.get(clazz);
if(classname==null)
{
classname = getUnqualifiedClassName(clazz);
StringTokenizer stok = new StringTokenizer(classname,"$");
while(stok.hasMoreTokens())
{
classname = stok.nextToken();
}
innerclassnamecache.put(clazz, classname);
}
return classname;
}
/**
* Get the package of a class.
* @return The name of the package.
*/
public static String getPackageName(Class clazz)
{
String classname = clazz.getName();
StringTokenizer stok = new StringTokenizer(classname,".");
String packagename = "";
while(stok.countTokens()>1)
{
packagename += stok.nextToken();
if(stok.countTokens()>1)
{
packagename +=".";
}
}
return packagename;
}
/**
* Get a field of the class,
* or any of it's superclasses.
* Unlike {@link Class#getField(String)},
* this will also return nonpublic fields
* (except when running in netscape :-( ).
* @param clazz The class to search.
* @param name The name of the field to search for.
* @return The field (or null if not found).
*/
public static Field getField(Class clazz, String name)
{
Field field = null;
Class cls = clazz;
while(field==null && cls!=null && !cls.equals(Object.class))
{
try
{
field = cls.getDeclaredField(name);
}
catch(Exception e)
{
//e.printStackTrace();
cls = cls.getSuperclass();
}
}
// Netscape security workaround.
// Will only find public methods :-(.
if(field==null)
{
try
{
field = clazz.getField(name);
}
catch(Exception e){}
}
return field;
}
/**
* Get a cached field.
* @param clazz The clazz.
* @param name The name.
* @return The field.
*/
public static Field getCachedField(Class clazz, String name) throws NoSuchFieldException
{
Field ret = null;
HashMap fields = (HashMap)fieldcache.get(clazz);
if(fields==null)
{
fields = new HashMap();
fieldcache.put(clazz, fields);
}
Object o = fields.get(name);
if(o instanceof Field)
{
ret = (Field)o;
}
else if(o==null)
{
try
{
//ret = getField(clazz, name);
ret = clazz.getField(name);
fields.put(name, ret);
}
catch(NoSuchFieldException e)
{
fields.put(name, e);
throw e;
}
}
else
{
throw (NoSuchFieldException)o;
}
return ret;
}
/**
* Get the declared object, doesnt cumulate through superclasses.
* @param o The object.
* @param fieldname The name of the array.
*/
public static Object getDeclared(Object o, String fieldname)
{
Class clazz = o.getClass();
Object os = null;
Field field = null;
try
{
field = clazz.getDeclaredField(fieldname);
os = field.get(o);
}
catch(Exception e)
{
//System.out.println("could not find: "+fieldname+" in "+o);
//e.printStackTrace();
}
return os;
}
/**
* Get a method of the class.
* Unlike {@link Class#getMethod(String, Class[])},
* this uses the methodcache.
* @param clazz The class to search.
* @param name The name of the method to search for.
* @param types The parameter types.
* @return The method (or null if not found).
*/
public static Method getMethod(Class clazz, String name, Class[] types)
{
Method meth = null;
Method[] ms = getMethods(clazz, name);
for(int i=0; i<ms.length; i++)
{
Class[] ptypes = ms[i].getParameterTypes();
boolean match = ptypes.length==types.length;
for(int j=0; match && j<ptypes.length; j++)
{
match = ptypes[j].equals(types[j]);
}
if(match)
{
meth = ms[i];
break;
}
}
return meth;
}
/**
* Get public method(s) of the class by name.
* @param clazz The class to search.
* @param name The name of the method to search for.
* @return The method(s).
*/
public static Method[] getMethods(Class clazz, String name)
{
Map map = (Map)methodcache.get(clazz);
if(map==null)
{
map = SCollection.createHashMap();
methodcache.put(clazz, map);
}
Method[] ret = (Method[])map.get(name);
if(ret==null)
{
Method[] ms = clazz.getMethods();
int cnt = 0;
for(int i=0; i<ms.length; i++)
{
if(ms[i].getName().equals(name))
{
cnt++;
}
else
{
ms[i] = null;
}
}
ret = new Method[cnt];
cnt = 0;
for(int i=0; i<ms.length; i++)
{
if(ms[i]!=null)
ret[cnt++] = ms[i];
}
map.put(name, ret);
}
return ret;
}
/**
* Find a class.
* When the class name is not fully qualified, the list of
* imported packages is searched for the class.
* @param clname The class name.
* @param imports The comma separated list of imported packages.
* @throws ClassNotFoundException when the class is not found in the imports.
*/
public static Class findClass(String clname, String[] imports, ClassLoader classloader)
throws ClassNotFoundException
{
Class clazz = findClass0(clname, imports, classloader);
if(clazz==null)
{
throw new ClassNotFoundException("Class "+clname+" not found in imports");//: "+SUtil.arrayToString(imports));
}
return clazz;
}
/**
* Find a class. Also supports basic types and arrays.
* When the class name is not fully qualified, the list of
* imported packages is searched for the class.
* @param clname The class name.
* @param imports The comma separated list of imported packages.
* @return null, when the class is not found in the imports.
*/
public static Class findClass0(String clname, String[] imports, ClassLoader classloader)
{
Class clazz = null;
// System.out.println("+++fC: "+clname+" "+imports);
// Try to find in cache.
boolean cachemiss = false;
Map cache = (Map)classcache.get(classloader);
if(cache!=null)
{
// Hack!!! Tuple should be immutable, but currently doesn't copy entries, so we can do this to reduce number of created tuples
Object[] entities = new Object[]{clname, null};
Tuple tuple = new Tuple(entities);
if(cache.containsKey(tuple))
{
clazz = (Class)cache.get(tuple);
}
else
{
cachemiss = true;
}
if(clazz==null && imports!=null)
{
for(int i=0; clazz==null && i<imports.length; i++)
{
entities[1] = imports[i];
if(cache.containsKey(tuple))
{
clazz = (Class)cache.get(tuple);
}
else
{
cachemiss = true;
}
}
}
if(clazz==null)
{
entities[1] = "java.lang.*";
if(cache.containsKey(tuple))
{
clazz = (Class)cache.get(tuple);
}
else
{
cachemiss = true;
}
}
}
else
{
cachemiss = true;
cache = Collections.synchronizedMap(new HashMap());
classcache.put(classloader, cache);
}
if(clazz==null && cachemiss)
{
// Try to find fully qualified.
clazz = classForName0(clname, classloader);
cache.put(new Tuple(clname, null), clazz);
// Try to find in imports.
if(clazz==null && imports!=null)
{
String clwoa = clname;
String brackets = "";
while(clwoa.endsWith("[]"))
{
clwoa = clwoa.substring(0, clwoa.length()-2);
brackets += "[]";
}
for(int i=0; clazz==null && i<imports.length; i++)
{
// Package import
if(imports[i].endsWith(".*"))
{
clazz = classForName0(
imports[i].substring(0, imports[i].length()-1) + clname, classloader);
// System.out.println("+++cFN1: "+imp.substring(0, imp.length()-1) + clname+", "+clazz);
}
// Class import
else if(imports[i].endsWith(clwoa))
{
clazz = classForName0(imports[i]+brackets, classloader);
// System.out.println("+++cFN2: "+imp+", "+clazz);
}
cache.put(new Tuple(clname, imports[i]), clazz);
}
}
// Try java.lang (imported by default).
if(clazz==null)
{
clazz = classForName0("java.lang." + clname, classloader);
cache.put(new Tuple(clname, "java.lang.*"), clazz);
}
// if(clazz==null)
// {
// System.err.println("Class not found: "+clname+", "+SUtil.arrayToString(imports));
// }
}
return clazz;
}
/**
* Match the given argument types to a set
* of parameter type arrays.
* The argument type array may contain null values,
* when an argument type is unknown.
* For convenience, the length of a parameter type array
* does not have to equal the argument array length.
* (although it will of course never match).
* The method returns the indices of the matching
* parameter type arrays. An empty array is returned,
* if no match is found. The returned matches are sorted
* by quality (best match first).
* @param argtypes The array of argument types.
* @param paramtypes The array of parameter type arrays.
* @return The indices of the matching parameter type arrays.
*/
public static int[] matchArgumentTypes(Class[] argtypes, Class[][] paramtypes)
{
// Store matches in array (store quality, -1 = no match).
int[] matches = new int[paramtypes.length];
int hq = 0; // Highest quality value.
int cnt = 0; // Number of matches.
for(int i=0; i<paramtypes.length; i++)
{
if(paramtypes[i].length==argtypes.length)
{
for(int j=0; j<argtypes.length && matches[i]!=-1; j++)
{
// Check if parameter type matches argument type.
if(argtypes[j]!=null)
{
// No match.
if(!SReflect.isSupertype(paramtypes[i][j], argtypes[j]))
{
matches[i] = -1;
}
// Exact match.
else if(getWrappedType(paramtypes[i][j])
== getWrappedType(argtypes[j]))
{
// Increase quality.
matches[i]++;
if(matches[i]>hq)
hq = matches[i];
}
}
}
if(matches[i]!=-1)
cnt++;
}
else
{
matches[i] = -1;
}
}
// Create result array.
int[] ret = new int[cnt];
cnt=0;
// Insert indices by quality.
for(;hq>=0; hq--)
{
for(int i=0; i<matches.length; i++)
{
if(matches[i]==hq)
{
ret[cnt++] = i;
}
}
}
return ret;
}
/**
* Check if a class is a supertype of, or the same as another class.
* Maps basic types to wrapped types, and respects
* the basic type hierarchy.
* @param clazz1 The assumed supertype.
* @param clazz2 The assumed subtype.
* @return True, if clazz1 is a supertype of, or the same as clazz2.
*/
public static boolean isSupertype(Class clazz1, Class clazz2)
{
// Map basic types.
//System.out.println("a: "+clazz1.getName()+" "+clazz1.hashCode());
//System.out.println("b: "+clazz2.getName()+" "+clazz2.hashCode());
clazz1 = getWrappedType(clazz1);
clazz2 = getWrappedType(clazz2);
// Handle trivial case for speed.
if(clazz1==clazz2)
{
return true;
}
// Check number type hierarchy.
// Double.
else if(clazz1==Double.class && (clazz2==Float.class
|| clazz2==Long.class || clazz2==Integer.class
|| clazz2==Short.class || clazz2==Byte.class
|| clazz2==Character.class))
{
return true;
}
// Float.
else if(clazz1==Float.class && (clazz2==Long.class
|| clazz2==Integer.class || clazz2==Short.class
|| clazz2==Byte.class || clazz2==Character.class))
{
return true;
}
// Long.
else if(clazz1==Long.class && (clazz2==Integer.class
|| clazz2==Short.class || clazz2==Byte.class
|| clazz2==Character.class))
{
return true;
}
// Integer.
else if(clazz1==Integer.class && (clazz2==Short.class
|| clazz2==Byte.class || clazz2==Character.class))
{
return true;
}
// Short.
else if(clazz1==Short.class && clazz2==Byte.class)
{
return true;
}
// Standard case.
else
{
return clazz1.isAssignableFrom(clazz2);
}
}
/**
* Convert a value to the correct wrapped type.
* Assumes that the conversion is possible.
* @see #isSupertype(Class, Class)
* @param value The value.
* @param clazz The target clazz.
* @return The converted value.
*/
public static Object convertWrappedValue(Object value, Class clazz)
{
clazz = getWrappedType(clazz);
if(isSupertype(Number.class, clazz))
{
if(value instanceof Character)
{
value = new Integer(((Character)value).charValue());
}
Number num =null;
if(value!=null)
{
try
{
num = (Number)value;
}
catch(ClassCastException e)
{
System.out.println(":: "+value+" "+value.getClass()+" "+clazz);
}
if(clazz.equals(Double.class))
{
value = new Double(num.doubleValue());
}
else if(clazz.equals(Float.class))
{
value = new Float(num.floatValue());
}
else if(clazz.equals(Long.class))
{
value = new Long(num.longValue());
}
else if(clazz.equals(Integer.class))
{
value = new Integer(num.intValue());
}
else if(clazz.equals(Short.class))
{
value = new Short(num.shortValue());
}
}
}
return value;
}
/**
* Get an iterator for an arbitrary collection object.
* Supports iterators, enumerations, java.util.Collections,
* java.util.Maps, arrays. Null is converted to empty iterator.
* @param collection The collection object.
* @return An iterator over the collection.
* @throws IllegalArgumentException when argument is not
* one of (Iterator, Enumeration, Collection, Map, Array).
*/
public static Iterator getIterator(Object collection)
{
if(collection==null)
{
return Collections.EMPTY_LIST.iterator();
}
else if(collection instanceof Iterator)
{
return (Iterator)collection;
}
else if(collection instanceof Enumeration)
{
// Return enumeration wrapper.
final Enumeration eoc = (Enumeration)collection;
return new Iterator()
{
public boolean hasNext() {return eoc.hasMoreElements();}
public Object next() {return eoc.nextElement();}
public void remove(){throw new UnsupportedOperationException(
"remove() not supported for enumerations");}
};
}
else if(collection instanceof Collection)
{
return ((Collection)collection).iterator();
}
else if(collection instanceof Map)
{
return ((Map)collection).values().iterator();
}
else if(collection!=null && collection.getClass().isArray())
{
// Return array wrapper.
final Object array = collection;
return new Iterator()
{
int i=0;
public boolean hasNext() {return i<Array.getLength(array);}
public Object next() {return Array.get(array, i++);}
public void remove() {throw new UnsupportedOperationException(
"remove() not supported for arrays");}
};
}
else
{
throw new IllegalArgumentException("Cannot iterate over "+collection);
}
}
/**
* Test if object is some kind of collection.
* @param obj The object.
* @return True if is iterable.
*/
public static boolean isIterable(Object obj)
{
return obj instanceof Iterator
|| obj instanceof Enumeration
|| obj instanceof Collection
|| obj instanceof Map
|| obj!=null && obj.getClass().isArray();
}
/**
* Test if class is some kind of collection.
* @param class The class.
* @return True if is iterable.
*/
public static boolean isIterableClass(Class clazz)
{
return Iterator.class.isAssignableFrom(clazz)
|| Enumeration.class.isAssignableFrom(clazz)
|| Collection.class.isAssignableFrom(clazz)
|| Map.class.isAssignableFrom(clazz)
|| clazz.isArray();
}
protected static Object[] EMPTY_ARRAY = new Object[0];
/**
* Get an array for an arbitrary collection object.
* Supports iterators, enumerations, java.util.Collections,
* java.util.Maps, arrays. Null is converted to empty array.
* @param collection The collection object.
* @return An array over the collection.
* @throws IllegalArgumentException when argument is not
* one of (Iterator, Enumeration, Collection, Map, Array).
*/
public static Object getArray(Object collection)
{
if(collection==null)
{
return EMPTY_ARRAY;
}
else if(collection instanceof Iterator)
{
final Iterator it = (Iterator)collection;
List ret = new ArrayList();
while(it.hasNext())
ret.add(it.next());
return ret.toArray();
}
else if(collection instanceof Enumeration)
{
final Enumeration eoc = (Enumeration)collection;
List ret = new ArrayList();
while(eoc.hasMoreElements())
ret.add(eoc.nextElement());
return ret.toArray();
}
else if(collection instanceof Collection)
{
return ((Collection)collection).toArray();
}
else if(collection instanceof Map)
{
return ((Map)collection).values().toArray();
}
else if(collection!=null && collection.getClass().isArray())
{
return collection;
}
else
{
throw new IllegalArgumentException("Cannot iterate over "+collection);
}
}
/**
* Is an object instanceof a class or its superclasses.
* @param o The object.
* @param c The class.
* @return True, when o is instance of class c.
*/
public static boolean instanceOf(Object o, Class c)
{
return isSupertype(c, o.getClass());
}
/**
* Test if a class is an anonymous inner class.
* Checks if the name ends with $int.
*/
public static boolean isAnonymousInnerClass(Class clazz)
{
boolean ret = false;
String name = clazz.getName();
int idx = name.lastIndexOf('$');
if(idx!=-1)
{
ret = true;
char[] end = new char[name.length()-idx-1];
name.getChars(idx+1, name.length(), end, 0);
for(int i=0; ret && i<end.length; i++)
{
ret = end[i]>='0' && end[i]<='9';
}
}
return ret;
}
}