1 package org.testng.internal; 2 3 import java.lang.reflect.Constructor; 4 import java.lang.reflect.InvocationTargetException; 5 import java.lang.reflect.Method; 6 import java.lang.reflect.Modifier; 7 import java.util.Arrays; 8 import java.util.List; 9 import java.util.Map; 10 import java.util.Set; 11 import java.util.Vector; 12 13 import org.testng.IClass; 14 import org.testng.IMethodSelector; 15 import org.testng.IObjectFactory; 16 import org.testng.IObjectFactory2; 17 import org.testng.ITestObjectFactory; 18 import org.testng.TestNGException; 19 import org.testng.TestRunner; 20 import org.testng.annotations.IAnnotation; 21 import org.testng.annotations.IFactoryAnnotation; 22 import org.testng.annotations.IParametersAnnotation; 23 import org.testng.collections.Sets; 24 import org.testng.internal.annotations.IAnnotationFinder; 25 import org.testng.junit.IJUnitTestRunner; 26 import org.testng.xml.XmlTest; 27 28 /** 29 * Utility class for different class manipulations. 30 */ 31 public final class ClassHelper { 32 private static final String JUNIT_TESTRUNNER= "org.testng.junit.JUnitTestRunner"; 33 private static final String JUNIT_4_TESTRUNNER = "org.testng.junit.JUnit4TestRunner"; 34 35 /** The additional class loaders to find classes in. */ 36 private static final List<ClassLoader> m_classLoaders = new Vector<>(); 37 38 /** Add a class loader to the searchable loaders. */ addClassLoader(final ClassLoader loader)39 public static void addClassLoader(final ClassLoader loader) { 40 m_classLoaders.add(loader); 41 } 42 43 /** Hide constructor. */ ClassHelper()44 private ClassHelper() { 45 // Hide Constructor 46 } 47 newInstance(Class<T> clazz)48 public static <T> T newInstance(Class<T> clazz) { 49 try { 50 T instance = clazz.newInstance(); 51 52 return instance; 53 } 54 catch(IllegalAccessException iae) { 55 throw new TestNGException("Class " + clazz.getName() 56 + " does not have a no-args constructor", iae); 57 } 58 catch(InstantiationException ie) { 59 throw new TestNGException("Cannot instantiate class " + clazz.getName(), ie); 60 } 61 catch(ExceptionInInitializerError eiierr) { 62 throw new TestNGException("An exception occurred in static initialization of class " 63 + clazz.getName(), eiierr); 64 } 65 catch(SecurityException se) { 66 throw new TestNGException(se); 67 } 68 } 69 newInstance(Constructor<T> constructor, Object... parameters)70 public static <T> T newInstance(Constructor<T> constructor, Object... parameters) { 71 try { 72 return constructor.newInstance(parameters); 73 } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { 74 throw new TestNGException("Cannot instantiate class " + constructor.getDeclaringClass().getName(), e); 75 } 76 } 77 78 /** 79 * Tries to load the specified class using the context ClassLoader or if none, 80 * than from the default ClassLoader. This method differs from the standard 81 * class loading methods in that it does not throw an exception if the class 82 * is not found but returns null instead. 83 * 84 * @param className the class name to be loaded. 85 * 86 * @return the class or null if the class is not found. 87 */ forName(final String className)88 public static Class<?> forName(final String className) { 89 Vector<ClassLoader> allClassLoaders = new Vector<>(); 90 ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); 91 if (contextClassLoader != null) { 92 allClassLoaders.add(contextClassLoader); 93 } 94 if (m_classLoaders != null) { 95 allClassLoaders.addAll(m_classLoaders); 96 } 97 98 for (ClassLoader classLoader : allClassLoaders) { 99 if (null == classLoader) { 100 continue; 101 } 102 try { 103 return classLoader.loadClass(className); 104 } 105 catch(ClassNotFoundException ex) { 106 // With additional class loaders, it is legitimate to ignore ClassNotFoundException 107 if (null == m_classLoaders || m_classLoaders.size() == 0) { 108 logClassNotFoundError(className, ex); 109 } 110 } 111 } 112 113 try { 114 return Class.forName(className); 115 } 116 catch(ClassNotFoundException cnfe) { 117 logClassNotFoundError(className, cnfe); 118 return null; 119 } 120 } 121 logClassNotFoundError(String className, Exception ex)122 private static void logClassNotFoundError(String className, Exception ex) { 123 Utils.log("ClassHelper", 2, "Could not instantiate " + className 124 + " : Class doesn't exist (" + ex.getMessage() + ")"); 125 } 126 127 /** 128 * For the given class, returns the method annotated with @Factory or null 129 * if none is found. This method does not search up the superclass hierarchy. 130 * If more than one method is @Factory annotated, a TestNGException is thrown. 131 * @param cls The class to search for the @Factory annotation. 132 * @param finder The finder (JDK 1.4 or JDK 5.0+) use to search for the annotation. 133 * 134 * @return the @Factory <CODE>method</CODE> or null 135 */ findDeclaredFactoryMethod(Class<?> cls, IAnnotationFinder finder)136 public static ConstructorOrMethod findDeclaredFactoryMethod(Class<?> cls, 137 IAnnotationFinder finder) { 138 ConstructorOrMethod result = null; 139 140 for (Method method : getAvailableMethods(cls)) { 141 IFactoryAnnotation f = finder.findAnnotation(method, IFactoryAnnotation.class); 142 143 if (null != f) { 144 result = new ConstructorOrMethod(method); 145 result.setEnabled(f.getEnabled()); 146 break; 147 } 148 } 149 150 if (result == null) { 151 for (Constructor constructor : cls.getDeclaredConstructors()) { 152 IAnnotation f = finder.findAnnotation(constructor, IFactoryAnnotation.class); 153 if (f != null) { 154 result = new ConstructorOrMethod(constructor); 155 } 156 } 157 } 158 // If we didn't find anything, look for nested classes 159 // if (null == result) { 160 // Class[] subClasses = cls.getClasses(); 161 // for (Class subClass : subClasses) { 162 // result = findFactoryMethod(subClass, finder); 163 // if (null != result) { 164 // break; 165 // } 166 // } 167 // } 168 169 return result; 170 } 171 172 /** 173 * Extract all callable methods of a class and all its super (keeping in mind 174 * the Java access rules). 175 */ getAvailableMethods(Class<?> clazz)176 public static Set<Method> getAvailableMethods(Class<?> clazz) { 177 Set<Method> methods = Sets.newHashSet(); 178 methods.addAll(Arrays.asList(clazz.getDeclaredMethods())); 179 180 Class<?> parent = clazz.getSuperclass(); 181 while (null != parent) { 182 methods.addAll(extractMethods(clazz, parent, methods)); 183 parent = parent.getSuperclass(); 184 } 185 186 return methods; 187 } 188 createTestRunner(TestRunner runner)189 public static IJUnitTestRunner createTestRunner(TestRunner runner) { 190 try { 191 //try to get runner for JUnit 4 first 192 Class.forName("org.junit.Test"); 193 IJUnitTestRunner tr = (IJUnitTestRunner) ClassHelper.forName(JUNIT_4_TESTRUNNER).newInstance(); 194 tr.setTestResultNotifier(runner); 195 return tr; 196 } catch (Throwable t) { 197 Utils.log("ClassHelper", 2, "JUnit 4 was not found on the classpath"); 198 try { 199 //fallback to JUnit 3 200 Class.forName("junit.framework.Test"); 201 IJUnitTestRunner tr = (IJUnitTestRunner) ClassHelper.forName(JUNIT_TESTRUNNER).newInstance(); 202 tr.setTestResultNotifier(runner); 203 204 return tr; 205 } catch (Exception ex) { 206 Utils.log("ClassHelper", 2, "JUnit 3 was not found on the classpath"); 207 //there's no JUnit on the classpath 208 throw new TestNGException("Cannot create JUnit runner", ex); 209 } 210 } 211 } 212 extractMethods(Class<?> childClass, Class<?> clazz, Set<Method> collected)213 private static Set<Method> extractMethods(Class<?> childClass, Class<?> clazz, 214 Set<Method> collected) { 215 Set<Method> methods = Sets.newHashSet(); 216 217 Method[] declaredMethods = clazz.getDeclaredMethods(); 218 219 Package childPackage = childClass.getPackage(); 220 Package classPackage = clazz.getPackage(); 221 boolean isSamePackage = false; 222 223 if ((null == childPackage) && (null == classPackage)) { 224 isSamePackage = true; 225 } 226 if ((null != childPackage) && (null != classPackage)) { 227 isSamePackage = childPackage.getName().equals(classPackage.getName()); 228 } 229 230 for (Method method : declaredMethods) { 231 int methodModifiers = method.getModifiers(); 232 if ((Modifier.isPublic(methodModifiers) || Modifier.isProtected(methodModifiers)) 233 || (isSamePackage && !Modifier.isPrivate(methodModifiers))) { 234 if (!isOverridden(method, collected) && !Modifier.isAbstract(methodModifiers)) { 235 methods.add(method); 236 } 237 } 238 } 239 240 return methods; 241 } 242 isOverridden(Method method, Set<Method> collectedMethods)243 private static boolean isOverridden(Method method, Set<Method> collectedMethods) { 244 Class<?> methodClass = method.getDeclaringClass(); 245 Class<?>[] methodParams = method.getParameterTypes(); 246 247 for (Method m: collectedMethods) { 248 Class<?>[] paramTypes = m.getParameterTypes(); 249 if (method.getName().equals(m.getName()) 250 && methodClass.isAssignableFrom(m.getDeclaringClass()) 251 && methodParams.length == paramTypes.length) { 252 253 boolean sameParameters = true; 254 for (int i= 0; i < methodParams.length; i++) { 255 if (!methodParams[i].equals(paramTypes[i])) { 256 sameParameters = false; 257 break; 258 } 259 } 260 261 if (sameParameters) { 262 return true; 263 } 264 } 265 } 266 267 return false; 268 } 269 createSelector(org.testng.xml.XmlMethodSelector selector)270 public static IMethodSelector createSelector(org.testng.xml.XmlMethodSelector selector) { 271 try { 272 Class<?> cls = Class.forName(selector.getClassName()); 273 return (IMethodSelector) cls.newInstance(); 274 } 275 catch(Exception ex) { 276 throw new TestNGException("Couldn't find method selector : " + selector.getClassName(), ex); 277 } 278 } 279 280 /** 281 * Create an instance for the given class. 282 */ createInstance(Class<?> declaringClass, Map<Class, IClass> classes, XmlTest xmlTest, IAnnotationFinder finder, ITestObjectFactory objectFactory)283 public static Object createInstance(Class<?> declaringClass, 284 Map<Class, IClass> classes, 285 XmlTest xmlTest, 286 IAnnotationFinder finder, 287 ITestObjectFactory objectFactory) 288 { 289 if (objectFactory instanceof IObjectFactory) { 290 return createInstance1(declaringClass, classes, xmlTest, finder, 291 (IObjectFactory) objectFactory); 292 } else if (objectFactory instanceof IObjectFactory2) { 293 return createInstance2(declaringClass, (IObjectFactory2) objectFactory); 294 } else { 295 throw new AssertionError("Unknown object factory type:" + objectFactory); 296 } 297 } 298 createInstance2(Class<?> declaringClass, IObjectFactory2 objectFactory)299 private static Object createInstance2(Class<?> declaringClass, IObjectFactory2 objectFactory) { 300 return objectFactory.newInstance(declaringClass); 301 } 302 createInstance1(Class<?> declaringClass, Map<Class, IClass> classes, XmlTest xmlTest, IAnnotationFinder finder, IObjectFactory objectFactory)303 public static Object createInstance1(Class<?> declaringClass, 304 Map<Class, IClass> classes, 305 XmlTest xmlTest, 306 IAnnotationFinder finder, 307 IObjectFactory objectFactory) { 308 Object result = null; 309 310 try { 311 312 // 313 // Any annotated constructor? 314 // 315 Constructor<?> constructor = findAnnotatedConstructor(finder, declaringClass); 316 if (null != constructor) { 317 IParametersAnnotation annotation = finder.findAnnotation(constructor, IParametersAnnotation.class); 318 319 String[] parameterNames = annotation.getValue(); 320 Object[] parameters = Parameters.createInstantiationParameters(constructor, 321 "@Parameters", 322 finder, 323 parameterNames, 324 xmlTest.getAllParameters(), 325 xmlTest.getSuite()); 326 result = objectFactory.newInstance(constructor, parameters); 327 } 328 329 // 330 // No, just try to instantiate the parameterless constructor (or the one 331 // with a String) 332 // 333 else { 334 335 // If this class is a (non-static) nested class, the constructor contains a hidden 336 // parameter of the type of the enclosing class 337 Class<?>[] parameterTypes = new Class[0]; 338 Object[] parameters = new Object[0]; 339 Class<?> ec = getEnclosingClass(declaringClass); 340 boolean isStatic = 0 != (declaringClass.getModifiers() & Modifier.STATIC); 341 342 // Only add the extra parameter if the nested class is not static 343 if ((null != ec) && !isStatic) { 344 parameterTypes = new Class[] { ec }; 345 346 // Create an instance of the enclosing class so we can instantiate 347 // the nested class (actually, we reuse the existing instance). 348 IClass enclosingIClass = classes.get(ec); 349 Object[] enclosingInstances; 350 if (null != enclosingIClass) { 351 enclosingInstances = enclosingIClass.getInstances(false); 352 if ((null == enclosingInstances) || (enclosingInstances.length == 0)) { 353 Object o = objectFactory.newInstance(ec.getConstructor(parameterTypes)); 354 enclosingIClass.addInstance(o); 355 enclosingInstances = new Object[] { o }; 356 } 357 } 358 else { 359 enclosingInstances = new Object[] { ec.newInstance() }; 360 } 361 Object enclosingClassInstance = enclosingInstances[0]; 362 363 // Utils.createInstance(ec, classes, xmlTest, finder); 364 parameters = new Object[] { enclosingClassInstance }; 365 } // isStatic 366 367 Constructor<?> ct; 368 try { 369 ct = declaringClass.getDeclaredConstructor(parameterTypes); 370 } 371 catch (NoSuchMethodException ex) { 372 ct = declaringClass.getDeclaredConstructor(String.class); 373 parameters = new Object[] { "Default test name" }; 374 // If ct == null here, we'll pass a null 375 // constructor to the factory and hope it can deal with it 376 } 377 result = objectFactory.newInstance(ct, parameters); 378 } 379 } 380 catch (TestNGException ex) { 381 throw ex; 382 // throw new TestNGException("Couldn't instantiate class:" + declaringClass); 383 } 384 catch (NoSuchMethodException ex) { 385 } 386 catch (Throwable cause) { 387 // Something else went wrong when running the constructor 388 throw new TestNGException("An error occurred while instantiating class " 389 + declaringClass.getName() + ": " + cause.getMessage(), cause); 390 } 391 392 if (result == null) { 393 if (! Modifier.isPublic(declaringClass.getModifiers())) { 394 //result should not be null 395 throw new TestNGException("An error occurred while instantiating class " 396 + declaringClass.getName() + ". Check to make sure it can be accessed/instantiated."); 397 // } else { 398 // Utils.log(ClassHelper.class.getName(), 2, "Couldn't instantiate class " + declaringClass); 399 } 400 } 401 402 return result; 403 } 404 405 /** 406 * Class.getEnclosingClass() only exists on JDK5, so reimplementing it 407 * here. 408 */ getEnclosingClass(Class<?> declaringClass)409 private static Class<?> getEnclosingClass(Class<?> declaringClass) { 410 Class<?> result = null; 411 412 String className = declaringClass.getName(); 413 int index = className.indexOf("$"); 414 if (index != -1) { 415 String ecn = className.substring(0, index); 416 try { 417 result = Class.forName(ecn); 418 } 419 catch (ClassNotFoundException e) { 420 e.printStackTrace(); 421 } 422 } 423 424 return result; 425 } 426 427 /** 428 * Find the best constructor given the parameters found on the annotation 429 */ findAnnotatedConstructor(IAnnotationFinder finder, Class<?> declaringClass)430 private static Constructor<?> findAnnotatedConstructor(IAnnotationFinder finder, 431 Class<?> declaringClass) { 432 Constructor<?>[] constructors = declaringClass.getDeclaredConstructors(); 433 434 for (Constructor<?> result : constructors) { 435 IParametersAnnotation annotation = finder.findAnnotation(result, IParametersAnnotation.class); 436 437 if (null != annotation) { 438 String[] parameters = annotation.getValue(); 439 Class<?>[] parameterTypes = result.getParameterTypes(); 440 if (parameters.length != parameterTypes.length) { 441 throw new TestNGException("Parameter count mismatch: " + result + "\naccepts " 442 + parameterTypes.length 443 + " parameters but the @Test annotation declares " 444 + parameters.length); 445 } 446 else { 447 return result; 448 } 449 } 450 } 451 452 return null; 453 } 454 tryOtherConstructor(Class<T> declaringClass)455 public static <T> T tryOtherConstructor(Class<T> declaringClass) { 456 T result; 457 try { 458 // Special case for inner classes 459 if (declaringClass.getModifiers() == 0) { 460 return null; 461 } 462 463 Constructor<T> ctor = declaringClass.getConstructor(String.class); 464 result = ctor.newInstance("Default test name"); 465 } 466 catch (Exception e) { 467 String message = e.getMessage(); 468 if ((message == null) && (e.getCause() != null)) { 469 message = e.getCause().getMessage(); 470 } 471 String error = "Could not create an instance of class " + declaringClass 472 + ((message != null) ? (": " + message) : "") 473 + ".\nPlease make sure it has a constructor that accepts either a String or no parameter."; 474 throw new TestNGException(error); 475 } 476 477 return result; 478 } 479 480 /** 481 * When given a file name to form a class name, the file name is parsed and divided 482 * into segments. For example, "c:/java/classes/com/foo/A.class" would be divided 483 * into 6 segments {"C:" "java", "classes", "com", "foo", "A"}. The first segment 484 * actually making up the class name is [3]. This value is saved in m_lastGoodRootIndex 485 * so that when we parse the next file name, we will try 3 right away. If 3 fails we 486 * will take the long approach. This is just a optimization cache value. 487 */ 488 private static int m_lastGoodRootIndex = -1; 489 490 /** 491 * Returns the Class object corresponding to the given name. The name may be 492 * of the following form: 493 * <ul> 494 * <li>A class name: "org.testng.TestNG"</li> 495 * <li>A class file name: "/testng/src/org/testng/TestNG.class"</li> 496 * <li>A class source name: "d:\testng\src\org\testng\TestNG.java"</li> 497 * </ul> 498 * 499 * @param file 500 * the class name. 501 * @return the class corresponding to the name specified. 502 */ fileToClass(String file)503 public static Class<?> fileToClass(String file) { 504 Class<?> result = null; 505 506 if(!file.endsWith(".class") && !file.endsWith(".java")) { 507 // Doesn't end in .java or .class, assume it's a class name 508 509 if (file.startsWith("class ")) { 510 file = file.substring("class ".length()); 511 } 512 513 result = ClassHelper.forName(file); 514 515 if (null == result) { 516 throw new TestNGException("Cannot load class from file: " + file); 517 } 518 519 return result; 520 } 521 522 int classIndex = file.lastIndexOf(".class"); 523 if (-1 == classIndex) { 524 classIndex = file.lastIndexOf(".java"); 525 // 526 // if(-1 == classIndex) { 527 // result = ClassHelper.forName(file); 528 // 529 // if (null == result) { 530 // throw new TestNGException("Cannot load class from file: " + file); 531 // } 532 // 533 // return result; 534 // } 535 // 536 } 537 538 // Transforms the file name into a class name. 539 540 // Remove the ".class" or ".java" extension. 541 String shortFileName = file.substring(0, classIndex); 542 543 // Split file name into segments. For example "c:/java/classes/com/foo/A" 544 // becomes {"c:", "java", "classes", "com", "foo", "A"} 545 String[] segments = shortFileName.split("[/\\\\]", -1); 546 547 // 548 // Check if the last good root index works for this one. For example, if the previous 549 // name was "c:/java/classes/com/foo/A.class" then m_lastGoodRootIndex is 3 and we 550 // try to make a class name ignoring the first m_lastGoodRootIndex segments (3). This 551 // will succeed rapidly if the path is the same as the one from the previous name. 552 // 553 if (-1 != m_lastGoodRootIndex) { 554 555 StringBuilder className = new StringBuilder(segments[m_lastGoodRootIndex]); 556 for (int i = m_lastGoodRootIndex + 1; i < segments.length; i++) { 557 className.append(".").append(segments[i]); 558 } 559 560 result = ClassHelper.forName(className.toString()); 561 562 if (null != result) { 563 return result; 564 } 565 } 566 567 // 568 // We haven't found a good root yet, start by resolving the class from the end segment 569 // and work our way up. For example, if we start with "c:/java/classes/com/foo/A" 570 // we'll start by resolving "A", then "foo.A", then "com.foo.A" until something 571 // resolves. When it does, we remember the path we are at as "lastGoodRoodIndex". 572 // 573 574 // TODO CQ use a StringBuffer here 575 String className = null; 576 for (int i = segments.length - 1; i >= 0; i--) { 577 if (null == className) { 578 className = segments[i]; 579 } 580 else { 581 className = segments[i] + "." + className; 582 } 583 584 result = ClassHelper.forName(className); 585 586 if (null != result) { 587 m_lastGoodRootIndex = i; 588 break; 589 } 590 } 591 592 if (null == result) { 593 throw new TestNGException("Cannot load class from file: " + file); 594 } 595 596 return result; 597 } 598 599 } 600