Home | History | Annotate | Download | only in internal
      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. */
     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