/**
* Copyright (c) 2014, the Railo Company Ltd.
* Copyright (c) 2015, Lucee Assosication Switzerland
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
package lucee.commons.lang;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLDecoder;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import lucee.commons.collection.MapFactory;
import lucee.commons.io.CharsetUtil;
import lucee.commons.io.FileUtil;
import lucee.commons.io.IOUtil;
import lucee.commons.io.SystemUtil;
import lucee.commons.io.res.util.ResourceClassLoader;
import lucee.runtime.config.Config;
import lucee.runtime.config.Identification;
import lucee.runtime.exp.PageException;
import lucee.runtime.op.Caster;
import lucee.runtime.osgi.OSGiUtil;
import lucee.runtime.type.Array;
import lucee.runtime.type.util.ListUtil;
import org.osgi.framework.BundleException;
import org.osgi.framework.Version;
public final class ClassUtil {
/**
* @param className
* @return
* @throws ClassException
* @throws PageException
*/
public static Class toClass(String className) throws ClassException {
return loadClass(className);
}
private static Class checkPrimaryTypes(String className, Class defaultValue) {
String lcClassName=className.toLowerCase();
boolean isRef=false;
if(lcClassName.startsWith("java.lang.")){
lcClassName=lcClassName.substring(10);
isRef=true;
}
if(lcClassName.equals("void") || className.equals("[V")) {
return void.class;
}
if(lcClassName.equals("boolean") || className.equals("[Z")) {
if(isRef) return Boolean.class;
return boolean.class;
}
if(lcClassName.equals("byte") || className.equals("[B")) {
if(isRef) return Byte.class;
return byte.class;
}
if(lcClassName.equals("int") || className.equals("[I")) {
return int.class;
}
if(lcClassName.equals("long") || className.equals("[J")) {
if(isRef) return Long.class;
return long.class;
}
if(lcClassName.equals("float") || className.equals("[F")) {
if(isRef) return Float.class;
return float.class;
}
if(lcClassName.equals("double") || className.equals("[D")) {
if(isRef) return Double.class;
return double.class;
}
if(lcClassName.equals("char") || className.equals("[C")) {
return char.class;
}
if(lcClassName.equals("short") || className.equals("[S")) {
if(isRef) return Short.class;
return short.class;
}
if(lcClassName.equals("integer")) return Integer.class;
if(lcClassName.equals("character")) return Character.class;
if(lcClassName.equals("object")) return Object.class;
if(lcClassName.equals("string")) return String.class;
if(lcClassName.equals("null")) return Object.class;
if(lcClassName.equals("numeric")) return Double.class;
return defaultValue;
}
public static Class<?> loadClassByBundle(String className, String name, String strVersion,Identification id) throws ClassException, BundleException {
// version
Version version=null;
if(!StringUtil.isEmpty(strVersion,true)) {
version=OSGiUtil.toVersion(strVersion.trim(),null);
if(version==null)
throw new ClassException("Version defintion ["+strVersion+"] is invalid.");
}
return loadClassByBundle(className, name, version,id);
}
public static Class loadClassByBundle(String className, String name, Version version,Identification id) throws BundleException, ClassException {
try {
return OSGiUtil.loadBundle(name, version, id, true).loadClass(className);
}
catch (ClassNotFoundException e) {
if(version==null)
throw new ClassException("In the OSGi Bundle with the name ["+name+"] was no class with name ["+className+"] found.");
throw new ClassException("In the OSGi Bundle with the name ["+name+"] and the version ["+version+"] was no class with name ["+className+"] found.");
}
}
/**
* loads a class from a String classname
* @param className
* @param defaultValue
* @return matching Class
*/
public static Class loadClass(String className, Class defaultValue) {
// OSGI env
Class clazz= _loadClass(new OSGiBasedClassLoading(), className, null,null);
if(clazz!=null) return clazz;
// core classloader
clazz= _loadClass(new ClassLoaderBasedClassLoading(SystemUtil.getCoreClassLoader()), className, null,null);
if(clazz!=null) return clazz;
// loader classloader
clazz= _loadClass(new ClassLoaderBasedClassLoading(SystemUtil.getLoaderClassLoader()), className, null,null);
if(clazz!=null) return clazz;
return defaultValue;
}
/**
* loads a class from a String classname
* @param className
* @return matching Class
* @throws ClassException
*/
public static Class loadClass(String className) throws ClassException {
Set<Throwable> exceptions=new HashSet<Throwable>();
// OSGI env
Class clazz= _loadClass(new OSGiBasedClassLoading(), className, null,exceptions);
if(clazz!=null) return clazz;
// core classloader
clazz= _loadClass(new ClassLoaderBasedClassLoading(SystemUtil.getCoreClassLoader()), className, null,exceptions);
if(clazz!=null) return clazz;
// loader classloader
clazz= _loadClass(new ClassLoaderBasedClassLoading(SystemUtil.getLoaderClassLoader()), className, null,exceptions);
if(clazz!=null) return clazz;
String msg="cannot load class through its string name, because no definition for the class with the specified name ["+className+"] could be found";
if(exceptions.size()>0) {
StringBuilder detail=new StringBuilder();
Iterator<Throwable> it = exceptions.iterator();
Throwable t;
while(it.hasNext()) {
t=it.next();
detail.append(t.getClass().getName()).append(':').append(t.getMessage()).append(';');
}
throw new ClassException(msg+" caused by ("+detail.toString()+")");
}
throw new ClassException(msg);
}
public static Class loadClass(ClassLoader cl,String className, Class defaultValue) {
return loadClass(cl,className, defaultValue,null);
}
private static Class loadClass(ClassLoader cl,String className, Class defaultValue, Set<Throwable> exceptions) {
if(cl!=null) {
// TODO do not produce a resource classloader in the first place if there are no resources
if(cl instanceof ResourceClassLoader && ((ResourceClassLoader)cl).isEmpty()) {
ClassLoader p = ((ResourceClassLoader)cl).getParent();
if(p!=null)cl=p;
}
Class clazz= _loadClass(new ClassLoaderBasedClassLoading(cl), className, defaultValue,exceptions);
if(clazz!=null) return clazz;
}
// OSGI env
Class clazz= _loadClass(new OSGiBasedClassLoading(), className, null,exceptions);
if(clazz!=null) return clazz;
// core classloader
if(cl!=SystemUtil.getCoreClassLoader()) {
clazz= _loadClass(new ClassLoaderBasedClassLoading(SystemUtil.getCoreClassLoader()), className, null,exceptions);
if(clazz!=null) return clazz;
}
// loader classloader
if(cl!=SystemUtil.getLoaderClassLoader()) {
clazz= _loadClass(new ClassLoaderBasedClassLoading(SystemUtil.getLoaderClassLoader()), className, null,exceptions);
if(clazz!=null) return clazz;
}
return defaultValue;
}
/**
* loads a class from a specified Classloader with given classname
* @param className
* @param cl
* @return matching Class
* @throws ClassException
*/
public static Class loadClass(ClassLoader cl,String className) throws ClassException {
Set<Throwable> exceptions=new HashSet<Throwable>();
Class clazz = loadClass(cl,className,null,exceptions);
if(clazz!=null) return clazz;
String msg="cannot load class through its string name, because no definition for the class with the specified name ["+className+"] could be found";
if(exceptions.size()>0) {
StringBuilder detail=new StringBuilder();
Iterator<Throwable> it = exceptions.iterator();
Throwable t;
while(it.hasNext()) {
t=it.next();
detail.append(t.getClass().getName()).append(':').append(t.getMessage()).append(';');
}
throw new ClassException(msg+" caused by ("+detail.toString()+")");
}
throw new ClassException(msg);
}
/**
* loads a class from a specified Classloader with given classname
* @param className
* @param cl
* @return matching Class
*/
private static Class _loadClass(ClassLoading cl,String className, Class defaultValue, Set<Throwable> exceptions) {
className=className.trim();
Class clazz = checkPrimaryTypes(className, null);
if(clazz!=null) return clazz;
clazz=cl.loadClass(className, null,exceptions);
if(clazz!=null) return clazz;
// array in the format boolean[] or java.lang.String[]
if(!StringUtil.isEmpty(className) && className.endsWith("[]")) {
StringBuilder pureCN=new StringBuilder(className);
int dimensions=0;
do{
pureCN.delete(pureCN.length()-2, pureCN.length());
dimensions++;
}
while(pureCN.lastIndexOf("[]")==pureCN.length()-2);
clazz = _loadClass(cl, pureCN.toString(), null,exceptions);
if(clazz!=null) {
for(int i=0;i<dimensions;i++)clazz=toArrayClass(clazz);
return clazz;
}
}
// array in the format [C or [Ljava.lang.String;
else if(!StringUtil.isEmpty(className) && className.charAt(0)=='[') {
StringBuilder pureCN=new StringBuilder(className);
int dimensions=0;
do{
pureCN.delete(0, 1);
dimensions++;
}
while(pureCN.charAt(0)=='[');
clazz = _loadClass(cl,pureCN.toString(), null,exceptions);
if(clazz!=null) {
for(int i=0;i<dimensions;i++)clazz=toArrayClass(clazz);
return clazz;
}
}
// class in format Ljava.lang.String;
else if(!StringUtil.isEmpty(className) && className.charAt(0)=='L' && className.endsWith(";")) {
className=className.substring(1,className.length()-1).replace('/', '.');
return _loadClass(cl,className, defaultValue,exceptions);
}
return defaultValue;
}
/**
* loads a class from a String classname
* @param clazz class to load
* @return matching Class
* @throws ClassException
*/
public static Object loadInstance(Class clazz) throws ClassException{
try {
return clazz.newInstance();
}
catch (InstantiationException e) {
throw new ClassException("the specified class object ["+clazz.getName()+"()] cannot be instantiated");
}
catch (IllegalAccessException e) {
throw new ClassException("can't load class because the currently executing method does not have access to the definition of the specified class");
}
}
public static Object loadInstance(String className) throws ClassException{
return loadInstance(loadClass(className));
}
public static Object loadInstance(ClassLoader cl, String className) throws ClassException{
return loadInstance(loadClass(cl,className));
}
/**
* loads a class from a String classname
* @param clazz class to load
* @return matching Class
*/
public static Object loadInstance(Class clazz, Object defaultValue){
try {
return clazz.newInstance();
}
catch(Throwable t) {
ExceptionUtil.rethrowIfNecessary(t);
return defaultValue;
}
}
public static Object loadInstance(String className, Object defaultValue){
Class clazz = loadClass(className,null);
if(clazz==null) return defaultValue;
return loadInstance(clazz,defaultValue);
}
public static Object loadInstance(ClassLoader cl, String className, Object defaultValue) {
Class clazz = loadClass(cl,className,null);
if(clazz==null) return defaultValue;
return loadInstance(clazz,defaultValue);
}
/**
* loads a class from a String classname
* @param clazz class to load
* @param args
* @return matching Class
* @throws ClassException
* @throws ClassException
* @throws InvocationTargetException
*/
public static Object loadInstance(Class clazz, Object[] args) throws ClassException, InvocationTargetException {
if(args==null || args.length==0) return loadInstance(clazz);
Class[] cArgs=new Class[args.length];
for(int i=0;i<args.length;i++) {
cArgs[i]=args[i].getClass();
}
try {
Constructor c = clazz.getConstructor(cArgs);
return c.newInstance(args);
}
catch (SecurityException e) {
throw new ClassException("there is a security violation (throwed by security manager)");
}
catch (NoSuchMethodException e) {
StringBuilder sb=new StringBuilder(clazz.getName());
char del='(';
for(int i=0;i<cArgs.length;i++) {
sb.append(del);
sb.append(cArgs[i].getName());
del=',';
}
sb.append(')');
throw new ClassException("there is no constructor with this ["+sb+"] signature for the class ["+clazz.getName()+"]");
}
catch (IllegalArgumentException e) {
throw new ClassException("has been passed an illegal or inappropriate argument");
}
catch (InstantiationException e) {
throw new ClassException("the specified class object ["+clazz.getName()+"] cannot be instantiated because it is an interface or is an abstract class");
}
catch (IllegalAccessException e) {
throw new ClassException("can't load class because the currently executing method does not have access to the definition of the specified class");
}
}
public static Object loadInstance(String className, Object[] args) throws ClassException, InvocationTargetException{
return loadInstance(loadClass(className),args);
}
public static Object loadInstance(ClassLoader cl, String className, Object[] args) throws ClassException, InvocationTargetException{
return loadInstance(loadClass(cl,className),args);
}
/**
* loads a class from a String classname
* @param clazz class to load
* @param args
* @return matching Class
*/
public static Object loadInstance(Class clazz, Object[] args, Object defaultValue) {
if(args==null || args.length==0) return loadInstance(clazz,defaultValue);
try {
Class[] cArgs=new Class[args.length];
for(int i=0;i<args.length;i++) {
if(args[i]==null)cArgs[i]=Object.class;
else cArgs[i]=args[i].getClass();
}
Constructor c = clazz.getConstructor(cArgs);
return c.newInstance(args);
}
catch(Throwable t) {
ExceptionUtil.rethrowIfNecessary(t);
return defaultValue;
}
}
public static Object loadInstance(String className, Object[] args, Object defaultValue){
Class clazz = loadClass(className,null);
if(clazz==null) return defaultValue;
return loadInstance(clazz,args,defaultValue);
}
public static Object loadInstance(ClassLoader cl, String className, Object[] args, Object defaultValue) {
Class clazz = loadClass(cl,className,null);
if(clazz==null) return defaultValue;
return loadInstance(clazz,args,defaultValue);
}
/**
* @return returns a string array of all pathes in classpath
*/
public static String[] getClassPath(Config config) {
Map<String,String> pathes=MapFactory.<String,String>getConcurrentMap();
String pathSeperator=System.getProperty("path.separator");
if(pathSeperator==null)pathSeperator=";";
// pathes from system properties
String strPathes=System.getProperty("java.class.path");
if(strPathes!=null) {
Array arr=ListUtil.listToArrayRemoveEmpty(strPathes,pathSeperator);
int len=arr.size();
for(int i=1;i<=len;i++) {
File file=FileUtil.toFile(Caster.toString(arr.get(i,""),"").trim());
if(file.exists())
try {
pathes.put(file.getCanonicalPath(),"");
} catch (IOException e) {}
}
}
// pathes from url class Loader (dynamic loaded classes)
getClassPathesFromLoader(new ClassUtil().getClass().getClassLoader(), pathes);
getClassPathesFromLoader(config.getClassLoader(), pathes);
Set set = pathes.keySet();
return (String[]) set.toArray(new String[set.size()]);
}
/**
* get class pathes from all url ClassLoaders
* @param cl URL Class Loader
* @param pathes Hashmap with allpathes
*/
private static void getClassPathesFromLoader(ClassLoader cl, Map pathes) {
if(cl instanceof URLClassLoader)
_getClassPathesFromLoader((URLClassLoader) cl, pathes);
}
private static void _getClassPathesFromLoader(URLClassLoader ucl, Map pathes) {
getClassPathesFromLoader(ucl.getParent(), pathes);
// get all pathes
URL[] urls=ucl.getURLs();
for(int i=0;i<urls.length;i++) {
File file=FileUtil.toFile(urls[i].getPath());
if(file.exists())
try {
pathes.put(file.getCanonicalPath(),"");
} catch (IOException e) {}
}
}
// CafeBabe (Java Magic Number)
private static final int ICA=202;//CA
private static final int IFE=254;//FE
private static final int IBA=186;//BA
private static final int IBE=190;//BE
// CF33 (Lucee Magic Number)
private static final int ICF=207;//CF
private static final int I33=51;//33
private static final byte BCA=(byte)ICA;//CA
private static final byte BFE=(byte)IFE;//FE
private static final byte BBA=(byte)IBA;//BA
private static final byte BBE=(byte)IBE;//BE
private static final byte BCF=(byte)ICF;//CF
private static final byte B33=(byte)I33;//33
/**
* check if given stream is a bytecode stream, if yes remove bytecode mark
* @param is
* @return is bytecode stream
* @throws IOException
*/
public static boolean isBytecode(InputStream is) throws IOException {
if(!is.markSupported())
throw new IOException("can only read input streams that support mark/reset");
is.mark(-1);
//print(bytes);
int first=is.read();
int second=is.read();
boolean rtn=(first==ICA && second==IFE && is.read()==IBA && is.read()==IBE);
is.reset();
return rtn;
}
public static boolean isEncryptedBytecode(InputStream is) throws IOException {
if(!is.markSupported())
throw new IOException("can only read input streams that support mark/reset");
is.mark(-1);
//print(bytes);
int first=is.read();
int second=is.read();
boolean rtn=(first==ICF && second==I33);
is.reset();
return rtn;
}
public static boolean isBytecode(byte[] barr){
if(barr.length<4) return false;
return (barr[0]==BCF && barr[1]==B33) || (barr[0]==BCA && barr[1]==BFE && barr[2]==BBA && barr[3]==BBE);
}
public static boolean isRawBytecode(byte[] barr){
if(barr.length<4) return false;
return (barr[0]==BCA && barr[1]==BFE && barr[2]==BBA && barr[3]==BBE);
}
public static boolean hasCF33Prefix(byte[] barr) {
if(barr.length<4) return false;
return (barr[0]==BCF && barr[1]==B33);
}
public static byte[] removeCF33Prefix(byte[] barr) {
if(!hasCF33Prefix(barr)) return barr;
byte[] dest = new byte[barr.length-10];
System.arraycopy(barr, 10, dest, 0, 10);
return dest;
}
public static String getName(Class clazz) {
if(clazz.isArray()){
return getName(clazz.getComponentType())+"[]";
}
return clazz.getName();
}
public static Method getMethodIgnoreCase(Class clazz, String methodName, Class[] args, Method defaultValue) {
Method[] methods = clazz.getMethods();
Method method;
Class[] params;
outer:for(int i=0;i<methods.length;i++){
method=methods[i];
if(method.getName().equalsIgnoreCase(methodName)){
params = method.getParameterTypes();
if(params.length==args.length){
for(int y=0;y<params.length;y++){
if(!params[y].equals(args[y])){
continue outer;
}
}
return method;
}
}
}
return defaultValue;
}
public static Method getMethodIgnoreCase(Class clazz, String methodName, Class[] args) throws ClassException {
Method res = getMethodIgnoreCase(clazz, methodName, args,null);
if(res!=null) return res;
throw new ClassException("class "+clazz.getName()+" has no method with name "+methodName);
}
/**
* return all field names as String array
* @param clazz class to get field names from
* @return field names
*/
public static String[] getFieldNames(Class clazz) {
Field[] fields = clazz.getFields();
String[] names=new String[fields.length];
for(int i=0;i<names.length;i++){
names[i]=fields[i].getName();
}
return names;
}
public static byte[] toBytes(Class clazz) throws IOException {
return IOUtil.toBytes(clazz.getClassLoader().getResourceAsStream(clazz.getName().replace('.','/')+".class"),true);
}
/**
* return a array class based on the given class (opposite from Class.getComponentType())
* @param clazz
* @return
*/
public static Class toArrayClass(Class clazz) {
return java.lang.reflect.Array.newInstance(clazz, 0).getClass();
}
public static Class<?> toComponentType(Class<?> clazz) {
Class<?> tmp;
while(true){
tmp=clazz.getComponentType();
if(tmp==null) break;
clazz=tmp;
}
return clazz;
}
/**
* returns the path to the directory or jar file that the class was loaded from
*
* @param clazz - the Class object to check, for a live object pass obj.getClass();
* @param defaultValue - a value to return in case the source could not be determined
* @return
*/
public static String getSourcePathForClass(Class clazz, String defaultValue) {
try {
String result = clazz.getProtectionDomain().getCodeSource().getLocation().getPath();
result = URLDecoder.decode(result, CharsetUtil.UTF8.name());
result = SystemUtil.fixWindowsPath(result);
return result;
}
catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);}
return defaultValue;
}
/**
* tries to load the class and returns the path that it was loaded from
*
* @param className - the name of the class to check
* @param defaultValue - a value to return in case the source could not be determined
* @return
*/
public static String getSourcePathForClass(String className, String defaultValue) {
try {
return getSourcePathForClass(ClassUtil.loadClass(className), defaultValue);
}
catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);}
return defaultValue;
}
/**
* extracts the package from a className, return null, if there is none.
* @param className
* @return
*/
public static String extractPackage(String className) {
if(className==null) return null;
int index=className.lastIndexOf('.');
if(index!=-1) return className.substring(0,index);
return null;
}
/**
* extracts the class name of a classname with package
* @param className
* @return
*/
public static String extractName(String className) {
if(className==null) return null;
int index=className.lastIndexOf('.');
if(index!=-1) return className.substring(index+1);
return className;
}
/**
* if no bundle is defined it is loaded the old way
* @param className
* @param bundleName
* @param bundleVersion
* @param id
* @return
* @throws ClassException
* @throws BundleException
*/
public static Class loadClass(String className, String bundleName, String bundleVersion, Identification id) throws ClassException, BundleException {
if(StringUtil.isEmpty(bundleName))
return loadClass(className);
return loadClassByBundle(className, bundleName, bundleVersion, id);
}
private static interface ClassLoading {
public Class<?> loadClass(String className, Class defaultValue);
public Class<?> loadClass(String className, Class defaultValue, Set<Throwable> exceptions);
}
private static class ClassLoaderBasedClassLoading implements ClassLoading {
private ClassLoader cl;
public ClassLoaderBasedClassLoading(ClassLoader cl){
this.cl=cl;
}
@Override
public Class<?> loadClass(String className, Class defaultValue) {
return loadClass(className, defaultValue,null);
}
@Override
public Class<?> loadClass(String className, Class defaultValue, Set<Throwable> exceptions){
className=className.trim();
try {
return cl.loadClass(className);
}
catch(Throwable t) {
ExceptionUtil.rethrowIfNecessary(t);
try {
return Class.forName(className, false, cl);
}
catch (Throwable t2) {ExceptionUtil.rethrowIfNecessary(t2);
if(exceptions!=null) {
exceptions.add(t2);
}
return defaultValue;
}
}
}
/*@Override
public Class<?> loadClass(String className) throws ClassException {
className=className.trim();
try {
return cl.loadClass(className);
}
catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);
try {
return Class.forName(className, false, cl);
}
catch (Throwable t2) {ExceptionUtil.rethrowIfNecessary(t2);
String msg=null;
if(t2 instanceof ClassNotFoundException || t2 instanceof NoClassDefFoundError) {
msg="["+t2.getClass().getName()+"] "+t2.getMessage();
}
if(StringUtil.isEmpty(msg)) msg="cannot load class through its string name, because no definition for the class with the specified name "
+ "["+className+"] could be found";
throw new ClassException(msg);
}
}
}*/
}
private static class OSGiBasedClassLoading implements ClassLoading {
@Override
public Class<?> loadClass(String className, Class defaultValue){
return OSGiUtil.loadClass(className, defaultValue);
}
@Override
public Class<?> loadClass(String className, Class defaultValue, Set<Throwable> exceptions){
return loadClass(className, defaultValue);
}
}
}