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}