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 &#64;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