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