1package org.testng.internal;
2
3import java.lang.reflect.Constructor;
4import java.lang.reflect.InvocationTargetException;
5import java.lang.reflect.Method;
6import java.lang.reflect.Modifier;
7import java.util.Arrays;
8import java.util.List;
9import java.util.Map;
10import java.util.Set;
11import java.util.Vector;
12
13import org.testng.IClass;
14import org.testng.IMethodSelector;
15import org.testng.IObjectFactory;
16import org.testng.IObjectFactory2;
17import org.testng.ITestObjectFactory;
18import org.testng.TestNGException;
19import org.testng.TestRunner;
20import org.testng.annotations.IAnnotation;
21import org.testng.annotations.IFactoryAnnotation;
22import org.testng.annotations.IParametersAnnotation;
23import org.testng.collections.Sets;
24import org.testng.internal.annotations.IAnnotationFinder;
25import org.testng.junit.IJUnitTestRunner;
26import org.testng.xml.XmlTest;
27
28/**
29 * Utility class for different class manipulations.
30 */
31public 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. */
39  public static void addClassLoader(final ClassLoader loader) {
40    m_classLoaders.add(loader);
41  }
42
43  /** Hide constructor. */
44  private ClassHelper() {
45    // Hide Constructor
46  }
47
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
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   */
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
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   */
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   */
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
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
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
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
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   */
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
299  private static Object createInstance2(Class<?> declaringClass, IObjectFactory2 objectFactory) {
300    return objectFactory.newInstance(declaringClass);
301  }
302
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   */
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   */
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
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   */
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