package com.laytonsmith.PureUtilities.Common;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Provides wrapper methods around some common methods that Class and some of
* the java.reflect package class left out.
*/
public class ClassUtils {
private static final Pattern ARRAY_COUNT_PATTERN = Pattern.compile("\\[\\]");
/**
* Returns the Class object, given the in-code class name. This takes into
* account inner classes not being handled the same normally, as well as ...
* for varargs, and [] for arrays. For instance, java.lang.String[] would
* return the class object for String[].class. Primitives are handled
* correctly as well. This works like Class.forName in all other regards,
* however.
*
* @param className The canonical class name.
* @return The class object.
* @throws ClassNotFoundException If the class can't be found.
*/
public static Class forCanonicalName(String className) throws ClassNotFoundException{
return forCanonicalName(className, false, false, null);
}
/**
* Returns the Class object, given the in-code class name. This takes into
* account inner classes not being handled the same normally, as well as ...
* for varargs, and [] for arrays. For instance, java.lang.String[] would
* return the class object for String[].class. Primitives are handled
* correctly as well. This works like Class.forName in all other regards,
* however.
*
* @param className The canonical class name
* @param initialize whether the class must be initialized
* @param classLoader classLoader from which the class must be loaded
* @return The class object.
* @throws ClassNotFoundException If the class can't be found.
*/
public static Class forCanonicalName(String className, boolean initialize, ClassLoader classLoader) throws ClassNotFoundException{
return forCanonicalName(className, true, initialize, classLoader);
}
/**
* Private version, which accepts the useInitializer parameter.
* @param className
* @param useInitializer
* @param initialize
* @param classLoader
* @return
* @throws ClassNotFoundException
*/
private static Class forCanonicalName(String className, boolean useInitializer, boolean initialize, ClassLoader classLoader) throws ClassNotFoundException {
className = StringUtils.replaceLast(className, "\\.\\.\\.", "[]");
//Of course primitives all need to be dealt with specially.
int arrays = 0;
Matcher m = ARRAY_COUNT_PATTERN.matcher(className);
while(m.find()){
arrays++;
}
String simpleName = className.replaceAll("\\[\\]", "");
String primitiveID = null;
Class primitiveClass = null;
if(null != simpleName)switch (simpleName) {
case "boolean":
primitiveID = "Z";
primitiveClass = boolean.class;
break;
case "byte":
primitiveID = "B";
primitiveClass = byte.class;
break;
case "short":
primitiveID = "S";
primitiveClass = short.class;
break;
case "int":
primitiveID = "I";
primitiveClass = int.class;
break;
case "long":
primitiveID = "J";
primitiveClass = long.class;
break;
case "float":
primitiveID = "F";
primitiveClass = float.class;
break;
case "double":
primitiveID = "D";
primitiveClass = double.class;
break;
case "char":
primitiveID = "C";
primitiveClass = char.class;
break;
}
if(primitiveClass != null){
if(arrays > 0){
//This will be dealt with below
className = StringUtils.stringMultiply(arrays, "[") + primitiveID;
} else {
//Class.forName doesn't know how to deal with this, so short circuit.
return primitiveClass;
}
} else {
if(arrays > 0){
//Ok, we need to get it from the canonical name
className = StringUtils.stringMultiply(arrays, "[") + "L" + simpleName + ";";
}
}
Class c = null;
try {
if(useInitializer){
c = Class.forName(className, initialize, classLoader);
} else {
c = Class.forName(className);
}
} catch (ClassNotFoundException ex) {
//Ok, try replacing the last . with $ as this may be an inner class
String name = className;
while (name.contains(".")) {
name = StringUtils.replaceLast(name, "\\.", "$");
try {
if(useInitializer){
c = Class.forName(name, initialize, classLoader);
} else {
c = Class.forName(name);
}
//Awesome, found it.
break;
} catch (ClassNotFoundException e) {
//No? Try again then.
}
}
if (c == null) {
//We really couldn't find it.
throw ex;
}
}
return c;
}
/**
* Returns the name of the class, as the JVM would output it. For instance,
* for an int, "I" is returned, for an array of Objects, "[Ljava/lang/Object;" is
* returned.
* @param clazz
* @return
*/
public static String getJVMName(Class clazz){
//For arrays, .getName() is fine.
if(clazz.isArray()){
return clazz.getName().replace(".", "/");
}
if(clazz == boolean.class){
return "Z";
} else if(clazz == byte.class){
return "B";
} else if(clazz == short.class){
return "S";
} else if(clazz == int.class){
return "I";
} else if(clazz == long.class){
return "J";
} else if(clazz == float.class){
return "F";
} else if(clazz == double.class){
return "D";
} else if(clazz == char.class){
return "C";
} else {
return "L" + clazz.getName().replace(".", "/") + ";";
}
}
/**
* Returns the common name of a class, as it would be typed out in source code.
* In general, this returns the same as Class.getName, but for arrays, it outputs
* <code>[[Ljava.lang.Object;</code> which would be better written as
* <code>java.lang.Object[][]</code>.
* @param c
* @return
*/
public static String getCommonName(Class c){
if(!c.isArray()){
//This is fine for non arrays.
return c.getName();
}
int arrayCount = c.getName().lastIndexOf("[") + 1;
Class cc = c.getComponentType();
while(cc.isArray()){
cc = cc.getComponentType();
}
return cc.getName() + StringUtils.stringMultiply(arrayCount, "[]");
}
/**
* Converts the binary name to the common name. For instance,
* for [Ljava/lang/Object;, java.lang.Object[] would be returned. The classes
* don't necessarily need to exist for this method to work.
* @param classname
* @return
*/
public static String getCommonNameFromJVMName(String classname){
int arrayCount = classname.lastIndexOf("[") + 1;
classname = classname.substring(arrayCount);
//ZBSIJDFC
if("Z".equals(classname)){
classname = "boolean";
} else if("B".equals(classname)){
classname = "byte";
} else if("S".equals(classname)){
classname = "short";
} else if("I".equals(classname)){
classname = "int";
} else if("J".equals(classname)){
classname = "long";
} else if("D".equals(classname)){
classname = "double";
} else if("F".equals(classname)){
classname = "float";
} else if("C".equals(classname)){
classname = "char";
} else if("V".equals(classname)){
return "void"; //special case
} else {
classname = classname.substring(1, classname.length() - 1).replace("/", ".").replace("$", ".");
}
return classname + StringUtils.stringMultiply(arrayCount, "[]");
}
/**
* Returns a list of all classes that the specified class can be validly cast to. This includes
* all super classes, as well as all interfaces (and superclasses of those interfaces, etc) and
* java.lang.Object, as well as the class itself.
* @param c The class to search for.
* @return
*/
public static Set<Class<?>> getAllCastableClasses(Class<?> c){
Set<Class<?>> ret = new HashSet<>();
getAllCastableClassesWithBlacklist(c, ret);
return ret;
}
/**
* Private version of {@link #getAllCastableClasses(java.lang.Class)}
* @param c
* @param blacklist
* @return
*/
private static Set<Class<?>> getAllCastableClassesWithBlacklist(Class<?> c, Set<Class<?>> blacklist){
if(blacklist.contains(c)){
return blacklist;
}
while(true){
blacklist.add(c);
Class<?> su = c.getSuperclass();
if(su == null){
return blacklist;
}
blacklist.add(su);
blacklist.addAll(getAllCastableClassesWithBlacklist(su, blacklist));
for(Class<?> iface : c.getInterfaces()){
blacklist.add(iface);
blacklist.addAll(getAllCastableClassesWithBlacklist(iface, blacklist));
}
c = su;
}
}
}