001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.commons.lang;
020
021import java.io.File;
022import java.io.IOException;
023import java.io.InputStream;
024import java.lang.reflect.Constructor;
025import java.lang.reflect.Field;
026import java.lang.reflect.InvocationTargetException;
027import java.lang.reflect.Method;
028import java.net.URL;
029import java.net.URLClassLoader;
030import java.net.URLDecoder;
031import java.util.Map;
032import java.util.Set;
033
034import lucee.commons.collection.MapFactory;
035import lucee.commons.io.FileUtil;
036import lucee.commons.io.IOUtil;
037import lucee.commons.io.SystemUtil;
038import lucee.runtime.PageContextImpl;
039import lucee.runtime.config.Config;
040import lucee.runtime.engine.ThreadLocalPageContext;
041import lucee.runtime.exp.PageException;
042import lucee.runtime.op.Caster;
043import lucee.runtime.type.Array;
044import lucee.runtime.type.util.ListUtil;
045
046
047public final class ClassUtil {
048
049        /**
050         * @param className
051         * @return
052         * @throws ClassException 
053         * @throws PageException
054         */
055        public static Class toClass(String className) throws ClassException {
056                return ClassUtil.loadClass(className);
057        }
058        
059        private static Class checkPrimaryTypes(String className, Class defaultValue) {
060                String lcClassName=className.toLowerCase();
061                boolean isRef=false;
062                
063                if(lcClassName.startsWith("java.lang.")){
064                        lcClassName=lcClassName.substring(10);
065                        isRef=true;
066                }
067
068                if(lcClassName.equals("void") || className.equals("[V"))        { 
069                        return void.class; 
070                }
071                if(lcClassName.equals("boolean") || className.equals("[Z"))     { 
072                        if(isRef) return Boolean.class;
073                        return boolean.class; 
074                }
075                if(lcClassName.equals("byte") || className.equals("[B"))        {
076                        if(isRef) return Byte.class;
077                        return byte.class; 
078                }
079                if(lcClassName.equals("int") || className.equals("[I")) {
080                        return int.class; 
081                }
082                if(lcClassName.equals("long") || className.equals("[J"))        {
083                        if(isRef) return Long.class;
084                        return long.class; 
085                }
086                if(lcClassName.equals("float") || className.equals("[F"))       {
087                        if(isRef) return Float.class;
088                        return float.class; 
089                }
090                if(lcClassName.equals("double") || className.equals("[D"))      {
091                        if(isRef) return Double.class;
092                        return double.class; 
093                }
094                if(lcClassName.equals("char") || className.equals("[C"))        {
095                        return char.class; 
096                }
097                if(lcClassName.equals("short") || className.equals("[S"))       {
098                        if(isRef) return Short.class;
099                        return short.class; 
100                }
101                
102                if(lcClassName.equals("integer"))       return Integer.class; 
103                if(lcClassName.equals("character"))     return Character.class; 
104                if(lcClassName.equals("object"))        return Object.class; 
105                if(lcClassName.equals("string"))        return String.class; 
106                if(lcClassName.equals("null"))          return Object.class; 
107                if(lcClassName.equals("numeric"))       return Double.class; 
108                
109                return defaultValue;
110        }
111        
112        
113        
114        /**
115         * loads a class from a String classname
116         * @param className
117         * @param defaultValue 
118         * @return matching Class
119         */
120        public static Class loadClass(String className, Class defaultValue) {
121                return loadClass(null,className,defaultValue);
122        }
123
124        /**
125         * loads a class from a String classname
126         * @param className
127         * @return matching Class
128         * @throws ClassException 
129         */
130        public static Class loadClass(String className) throws ClassException {
131                Config config = ThreadLocalPageContext.getConfig();
132                Class clazz = loadClass(config==null?null:config.getClassLoader(),className,null);
133                if(clazz!=null) return clazz;
134                throw new ClassException("cannot load class through its string name, because no definition for the class with the specified name ["+className+"] could be found");
135        }
136        
137        /**
138         * loads a class from a specified Classloader with given classname
139         * @param className
140         * @param cl 
141         * @return matching Class
142         */
143        public static Class loadClass(ClassLoader cl,String className, Class defaultValue) {
144                className=className.trim();
145                
146                Class clazz = checkPrimaryTypes(className, null);
147                if(clazz!=null) return clazz;
148                
149                if(cl==null){
150                        PageContextImpl pci = (PageContextImpl) ThreadLocalPageContext.get();
151                        if(pci!=null){
152                                try {
153                                        cl=pci.getClassLoader();
154                                }
155                                catch (IOException e) {}
156                        }
157                        if(cl==null) {
158                                Config config = ThreadLocalPageContext.getConfig();
159                                if(config!=null)cl=config.getClassLoader();
160                        }
161                }
162                
163                
164                
165                try {
166                        if(cl==null)return Class.forName(className.trim());
167                        return cl.loadClass(className.trim());
168                        
169                }
170                catch (ClassNotFoundException e) {
171                        try {
172                                return Class.forName(className, false, cl);
173                        } 
174                        catch (ClassNotFoundException e1) {
175                                // array in the format boolean[] or java.lang.String[]
176                                if(!StringUtil.isEmpty(className) && className.endsWith("[]")) {
177                                        StringBuilder pureCN=new StringBuilder(className);
178                                        int dimensions=0;
179                                        do{
180                                                pureCN.delete(pureCN.length()-2, pureCN.length());
181                                                dimensions++;
182                                        }
183                                        while(pureCN.lastIndexOf("[]")==pureCN.length()-2);
184                                        
185                                        clazz = loadClass(cl,pureCN.toString(),null);
186                                        if(clazz!=null) {
187                                                for(int i=0;i<dimensions;i++)clazz=toArrayClass(clazz);
188                                                return clazz;
189                                        }
190                                }
191                                // array in the format [C or [Ljava.lang.String;
192                                else if(!StringUtil.isEmpty(className) && className.charAt(0)=='[') {
193                                        StringBuilder pureCN=new StringBuilder(className);
194                                        int dimensions=0;
195                                        do{
196                                                pureCN.delete(0, 1);
197                                                dimensions++;
198                                        }
199                                        while(pureCN.charAt(0)=='[');
200                                        
201                                        clazz = loadClass(cl,pureCN.toString(),null);
202                                        if(clazz!=null) {
203                                                for(int i=0;i<dimensions;i++)clazz=toArrayClass(clazz);
204                                                return clazz;
205                                        }
206                                }
207                                // class in format Ljava.lang.String;
208                                else if(!StringUtil.isEmpty(className) && className.charAt(0)=='L' && className.endsWith(";")) {
209                                        className=className.substring(1,className.length()-1).replace('/', '.');
210                                        return loadClass(cl, className,defaultValue);
211                                }
212                                
213                                return defaultValue;
214                        }
215                }
216        }
217
218        /**
219         * loads a class from a specified Classloader with given classname
220         * @param className
221         * @param cl 
222         * @return matching Class
223         * @throws ClassException 
224         */
225        public static Class loadClass(ClassLoader cl,String className) throws ClassException {
226                Class clazz = loadClass(cl,className,null);
227                if(clazz!=null) return clazz;
228                throw new ClassException("cannot load class through its string name, because no definition for the class with the specified name ["+className+"] could be found");
229        }
230
231        /**
232         * loads a class from a String classname
233         * @param clazz class to load
234         * @return matching Class
235         * @throws ClassException 
236         */
237        public static Object loadInstance(Class clazz) throws ClassException{
238                try {
239                        return clazz.newInstance();
240                }
241                catch (InstantiationException e) {
242                        throw new ClassException("the specified class object ["+clazz.getName()+"()] cannot be instantiated");
243                }
244                catch (IllegalAccessException e) {
245                        throw new ClassException("can't load class because the currently executing method does not have access to the definition of the specified class");
246                }
247        }
248
249        public static Object loadInstance(String className) throws ClassException{
250                return loadInstance(loadClass(className));
251        }
252        public static Object loadInstance(ClassLoader cl, String className) throws ClassException{
253                return loadInstance(loadClass(cl,className));
254        }
255        
256        /**
257         * loads a class from a String classname
258         * @param clazz class to load
259         * @return matching Class
260         */
261        public static Object loadInstance(Class clazz, Object defaultValue){
262                try {
263                        return clazz.newInstance();
264                }
265                catch (Throwable t) {
266                ExceptionUtil.rethrowIfNecessary(t);
267                        return defaultValue;
268                }
269        }
270        
271        public static Object loadInstance(String className, Object deaultValue){
272                Class clazz = loadClass(className,null);
273                if(clazz==null) return deaultValue;
274                return loadInstance(clazz,deaultValue);
275        }
276        
277        public static Object loadInstance(ClassLoader cl, String className, Object deaultValue) {
278                Class clazz = loadClass(cl,className,null);
279                if(clazz==null) return deaultValue;
280                return loadInstance(clazz,deaultValue);
281        }
282        
283        /**
284         * loads a class from a String classname
285         * @param clazz class to load
286         * @param args 
287         * @return matching Class
288         * @throws ClassException 
289         * @throws ClassException 
290         * @throws InvocationTargetException 
291         */
292        public static Object loadInstance(Class clazz, Object[] args) throws ClassException, InvocationTargetException {
293                if(args==null || args.length==0) return loadInstance(clazz);
294                
295                Class[] cArgs=new Class[args.length];
296                for(int i=0;i<args.length;i++) {
297                        cArgs[i]=args[i].getClass();
298                }
299                
300                try {
301                        Constructor c = clazz.getConstructor(cArgs);
302                        return c.newInstance(args);
303                        
304                }
305                catch (SecurityException e) {
306                        throw new ClassException("there is a security violation (throwed by security manager)");
307                }
308                catch (NoSuchMethodException e) {
309                        
310                        StringBuilder sb=new StringBuilder(clazz.getName());
311                        char del='(';
312                        for(int i=0;i<cArgs.length;i++) {
313                                sb.append(del);
314                                sb.append(cArgs[i].getName());
315                                del=',';
316                        }
317                        sb.append(')');
318                        
319                        throw new ClassException("there is no constructor with this ["+sb+"] signature for the class ["+clazz.getName()+"]");
320                }
321                catch (IllegalArgumentException e) {
322                        throw new ClassException("has been passed an illegal or inappropriate argument");
323                }
324                catch (InstantiationException e) {
325                        throw new ClassException("the specified class object ["+clazz.getName()+"] cannot be instantiated because it is an interface or is an abstract class");
326                }
327                catch (IllegalAccessException e) {
328                        throw new ClassException("can't load class because the currently executing method does not have access to the definition of the specified class");
329                }
330        }
331
332        public static Object loadInstance(String className, Object[] args) throws ClassException, InvocationTargetException{
333                return loadInstance(loadClass(className),args);
334        }
335        
336        public static Object loadInstance(ClassLoader cl, String className, Object[] args) throws ClassException, InvocationTargetException{
337                return loadInstance(loadClass(cl,className),args);
338        }
339        
340        /**
341         * loads a class from a String classname
342         * @param clazz class to load
343         * @param args 
344         * @return matching Class
345         */
346        public static Object loadInstance(Class clazz, Object[] args, Object defaultValue) {
347                if(args==null || args.length==0) return loadInstance(clazz,defaultValue);
348                try {
349                        Class[] cArgs=new Class[args.length];
350                        for(int i=0;i<args.length;i++) {
351                                if(args[i]==null)cArgs[i]=Object.class;
352                                else cArgs[i]=args[i].getClass();
353                        }
354                        Constructor c = clazz.getConstructor(cArgs);
355                        return c.newInstance(args);
356                        
357                }
358                catch (Throwable t) {
359                ExceptionUtil.rethrowIfNecessary(t);
360                        return defaultValue;
361                }
362                
363        }
364        
365        public static Object loadInstance(String className, Object[] args, Object deaultValue){
366                Class clazz = loadClass(className,null);
367                if(clazz==null) return deaultValue;
368                return loadInstance(clazz,args,deaultValue);
369        }
370        
371        public static Object loadInstance(ClassLoader cl, String className, Object[] args, Object deaultValue) {
372                Class clazz = loadClass(cl,className,null);
373                if(clazz==null) return deaultValue;
374                return loadInstance(clazz,args,deaultValue);
375        }
376        
377        /**
378         * @return returns a string array of all pathes in classpath
379         */
380        public static String[] getClassPath(Config config) {
381
382        Map<String,String> pathes=MapFactory.<String,String>getConcurrentMap();
383                String pathSeperator=System.getProperty("path.separator");
384                if(pathSeperator==null)pathSeperator=";";
385                        
386        // pathes from system properties
387                String strPathes=System.getProperty("java.class.path");
388                if(strPathes!=null) {
389                        Array arr=ListUtil.listToArrayRemoveEmpty(strPathes,pathSeperator);
390                        int len=arr.size();
391                        for(int i=1;i<=len;i++) {
392                                File file=FileUtil.toFile(Caster.toString(arr.get(i,""),"").trim());
393                                if(file.exists())
394                                        try {
395                                                pathes.put(file.getCanonicalPath(),"");
396                                        } catch (IOException e) {}
397                        }
398                }
399                
400                
401        // pathes from url class Loader (dynamic loaded classes)
402                getClassPathesFromLoader(new ClassUtil().getClass().getClassLoader(), pathes);
403                getClassPathesFromLoader(config.getClassLoader(), pathes);
404                
405                Set set = pathes.keySet();
406                return (String[]) set.toArray(new String[set.size()]);
407        }
408        
409        /**
410         * get class pathes from all url ClassLoaders
411         * @param cl URL Class Loader
412         * @param pathes Hashmap with allpathes
413         */
414        private static void getClassPathesFromLoader(ClassLoader cl, Map pathes) {
415                if(cl instanceof URLClassLoader) 
416                        _getClassPathesFromLoader((URLClassLoader) cl, pathes);
417        }
418                
419        
420        private static void _getClassPathesFromLoader(URLClassLoader ucl, Map pathes) {
421                getClassPathesFromLoader(ucl.getParent(), pathes);
422                
423                // get all pathes
424                URL[] urls=ucl.getURLs();
425                
426                for(int i=0;i<urls.length;i++) {
427                        File file=FileUtil.toFile(urls[i].getPath());
428                        if(file.exists())
429                                try {
430                                        pathes.put(file.getCanonicalPath(),"");
431                                } catch (IOException e) {}
432                }
433        }
434        
435        // CafeBabe (Java Magic Number)
436        private static final int ICA=202;//CA 
437    private static final int IFE=254;//FE
438    private static final int IBA=186;//BA
439    private static final int IBE=190;//BE
440    
441    // CF33 (Lucee Magic Number)
442    private static final int ICF=207;//CF 
443    private static final int I33=51;//33
444    
445
446        private static final byte BCA=(byte)ICA;//CA 
447    private static final byte BFE=(byte)IFE;//FE
448    private static final byte BBA=(byte)IBA;//BA
449    private static final byte BBE=(byte)IBE;//BE
450    
451    private static final byte BCF=(byte)ICF;//CF 
452    private static final byte B33=(byte)I33;//33
453    
454    
455    /** 
456     * check if given stream is a bytecode stream, if yes remove bytecode mark 
457     * @param is 
458     * @return is bytecode stream 
459     * @throws IOException 
460     */ 
461    public static boolean isBytecode(InputStream is) throws IOException { 
462            if(!is.markSupported()) 
463                    throw new IOException("can only read input streams that support mark/reset"); 
464            is.mark(-1); 
465            //print(bytes);
466            int first=is.read();
467            int second=is.read();
468             boolean rtn=(first==ICA && second==IFE && is.read()==IBA && is.read()==IBE);
469            
470        is.reset(); 
471        return rtn; 
472    }
473    
474    public static boolean isEncryptedBytecode(InputStream is) throws IOException { 
475        if(!is.markSupported()) 
476                throw new IOException("can only read input streams that support mark/reset"); 
477        is.mark(-1); 
478        //print(bytes);
479        int first=is.read();
480        int second=is.read();
481         boolean rtn=(first==ICF && second==I33);
482        
483            is.reset(); 
484            return rtn; 
485        }
486    
487
488    public static boolean isBytecode(byte[] barr){ 
489        if(barr.length<4) return false;
490        return (barr[0]==BCA && barr[1]==BFE && barr[2]==BBA && barr[3]==BBE); 
491    }
492    public static boolean isRawBytecode(byte[] barr){ 
493        if(barr.length<4) return false;
494        return (barr[0]==BCA && barr[1]==BFE && barr[2]==BBA && barr[3]==BBE); 
495    }
496    
497    public static boolean isEncryptedBytecode(byte[] barr) { 
498        if(barr.length<4) return false;
499        return (barr[0]==BCF && barr[1]==B33); 
500    }
501    
502        /*public static byte[] removeCF33Prefix(byte[] barr) {
503                if(!hasCF33Prefix(barr)) return barr;
504        
505                byte[] dest = new byte[barr.length-10];
506                System.arraycopy(barr, 10, dest, 0, 10);
507                return dest;
508        }*/
509
510        public static String getName(Class clazz) {
511                if(clazz.isArray()){
512                        return getName(clazz.getComponentType())+"[]";
513                }
514                
515                return clazz.getName();
516        }
517
518        public static Method getMethodIgnoreCase(Class clazz, String methodName, Class[] args) throws ClassException {
519                Method[] methods = clazz.getMethods();
520                Method method;
521                Class[] params;
522                outer:for(int i=0;i<methods.length;i++){
523                        method=methods[i];
524                        if(method.getName().equalsIgnoreCase(methodName)){
525                                params = method.getParameterTypes();
526                                if(params.length==args.length){
527                                        for(int y=0;y<params.length;y++){
528                                                if(!params[y].equals(args[y])){
529                                                        continue outer;
530                                                }
531                                        }
532                                        return method;
533                                }
534                        }
535                }
536                
537                throw new ClassException("class "+clazz.getName()+" has no method with name "+methodName);
538        }
539
540        
541        /**
542         * return all field names as String array
543         * @param clazz class to get field names from
544         * @return field names
545         */
546        public static String[] getFieldNames(Class clazz) {
547                Field[] fields = clazz.getFields();
548                String[] names=new String[fields.length];
549                for(int i=0;i<names.length;i++){
550                        names[i]=fields[i].getName();
551                }
552                return names;
553        }
554
555        public static byte[] toBytes(Class clazz) throws IOException {
556                return IOUtil.toBytes(clazz.getClassLoader().getResourceAsStream(clazz.getName().replace('.','/')+".class"),true);
557        }
558
559        /**
560         * return a array class based on the given class (opposite from Class.getComponentType())
561         * @param clazz
562         * @return
563         */
564        public static Class toArrayClass(Class clazz) {
565                return java.lang.reflect.Array.newInstance(clazz, 0).getClass();
566        }
567
568
569
570        public static Class<?> toComponentType(Class<?> clazz) {
571                Class<?> tmp;
572                while(true){
573                        tmp=clazz.getComponentType();
574                        if(tmp==null) break;
575                        clazz=tmp;
576                }
577                return clazz;
578        }
579
580
581        /**
582         * returns the path to the directory or jar file that the class was loaded from
583         *
584         * @param clazz - the Class object to check, for a live object pass obj.getClass();
585         * @param defaultValue - a value to return in case the source could not be determined
586         * @return
587         */
588        public static String getSourcePathForClass(Class clazz, String defaultValue) {
589
590                try {
591
592                        String result = clazz.getProtectionDomain().getCodeSource().getLocation().getPath();
593                        result = URLDecoder.decode(result, Charset.UTF8);
594                        result = SystemUtil.fixWindowsPath(result);
595                        return result;
596                }
597                catch (Throwable t) {
598                ExceptionUtil.rethrowIfNecessary(t);
599        }
600
601                return defaultValue;
602        }
603        
604        /*
605        public static String getLocation(Class clazz) {
606                ClassLoader cl = clazz.getClassLoader();
607                if(cl instanceof ResourceClassLoader) {
608                        StringBuilder sb=new StringBuilder();
609                        Resource[] sources = ((ResourceClassLoader)cl).getResources();
610                        if(sources!=null)for(int i=0;i<sources.length;i++){
611                                if(i>0)sb.append(';');
612                                sb.append(sources[i]);
613                        }
614                        return sb.toString();
615                }
616                else if(cl instanceof PhysicalClassLoader) {
617                        return ((PhysicalClassLoader)cl).getDirectory().getAbsolutePath();
618                }
619                else if(cl instanceof ArchiveClassLoader) {
620                        return ((ArchiveClassLoader)cl).getDirectory(); // not supporting info about source YET
621                }
622                
623                try {
624                        URL loc = clazz.getProtectionDomain().getCodeSource().getLocation();
625                        if(loc!=null) return loc.toExternalForm();
626                        
627                }
628                catch (Throwable t) {
629                ExceptionUtil.rethrowIfNecessary(t);
630        }
631                return "";
632        }*/
633        
634
635
636        /**
637         * tries to load the class and returns the path that it was loaded from
638         *
639         * @param className - the name of the class to check
640         * @param defaultValue - a value to return in case the source could not be determined
641         * @return
642         */
643        public static String getSourcePathForClass(String className, String defaultValue) {
644
645                try {
646                        return  getSourcePathForClass(ClassUtil.loadClass(className), defaultValue);
647                }
648                catch (Throwable t) {
649                ExceptionUtil.rethrowIfNecessary(t);
650        }
651
652                return defaultValue;
653        }
654
655
656}