All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.testng.internal.ClassHelper Maven / Gradle / Ivy

There is a newer version: 7.10.1
Show newest version
package org.testng.internal;

import org.testng.IClass;
import org.testng.IMethodSelector;
import org.testng.IObjectFactory;
import org.testng.IObjectFactory2;
import org.testng.ITestObjectFactory;
import org.testng.TestNGException;
import org.testng.TestRunner;
import org.testng.annotations.IFactoryAnnotation;
import org.testng.annotations.IParametersAnnotation;
import org.testng.collections.Lists;
import org.testng.collections.Maps;
import org.testng.collections.Sets;
import org.testng.internal.annotations.IAnnotationFinder;
import org.testng.internal.reflect.ReflectionHelper;
import org.testng.junit.IJUnitTestRunner;
import org.testng.xml.XmlClass;
import org.testng.xml.XmlSuite;
import org.testng.xml.XmlTest;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

/** Utility class for different class manipulations. */
public final class ClassHelper {
  private static final String JUNIT_TESTRUNNER = "org.testng.junit.JUnitTestRunner";
  private static final String JUNIT_4_TESTRUNNER = "org.testng.junit.JUnit4TestRunner";

  /** The additional class loaders to find classes in. */
  private static final List classLoaders = new Vector<>();

  private static final String CANNOT_INSTANTIATE_CLASS = "Cannot instantiate class ";
  private static final String CLASS_HELPER = ClassHelper.class.getSimpleName();

  /**
   * When given a file name to form a class name, the file name is parsed and divided into segments.
   * For example, "c:/java/classes/com/foo/A.class" would be divided into 6 segments {"C:" "java",
   * "classes", "com", "foo", "A"}. The first segment actually making up the class name is [3]. This
   * value is saved in lastGoodRootIndex so that when we parse the next file name, we will try 3
   * right away. If 3 fails we will take the long approach. This is just a optimization cache value.
   */
  private static int lastGoodRootIndex = -1;

  /** Hide constructor. */
  private ClassHelper() {
    // Hide Constructor
  }

  /** Add a class loader to the searchable loaders. */
  public static void addClassLoader(final ClassLoader loader) {
    classLoaders.add(loader);
  }

  public static  T newInstance(Class clazz) {
    try {
      return clazz.newInstance();
    } catch (IllegalAccessException
        | InstantiationException
        | ExceptionInInitializerError
        | SecurityException
        | NullPointerException e) {
      throw new TestNGException(CANNOT_INSTANTIATE_CLASS + clazz.getName(), e);
    }
  }

  public static  T newInstanceOrNull(Class clazz) {
    try {
      Constructor constructor = clazz.getConstructor();
      return newInstance(constructor);
    } catch (ExceptionInInitializerError | SecurityException e) {
      throw new TestNGException(CANNOT_INSTANTIATE_CLASS + clazz.getName(), e);
    } catch (NoSuchMethodException e) {
      return null;
    }
  }

  public static  T newInstance(Constructor constructor, Object... parameters) {
    try {
      return constructor.newInstance(parameters);
    } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
      throw new TestNGException(
          CANNOT_INSTANTIATE_CLASS + constructor.getDeclaringClass().getName(), e);
    }
  }

  /**
   * Tries to load the specified class using the context ClassLoader or if none, than from the
   * default ClassLoader. This method differs from the standard class loading methods in that it
   * does not throw an exception if the class is not found but returns null instead.
   *
   * @param className the class name to be loaded.
   * @return the class or null if the class is not found.
   */
  public static Class forName(final String className) {
    List allClassLoaders = Lists.newArrayList();
    ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
    if (contextClassLoader != null) {
      allClassLoaders.add(contextClassLoader);
    }
    allClassLoaders.addAll(classLoaders);

    for (ClassLoader classLoader : allClassLoaders) {
      if (null == classLoader) {
        continue;
      }
      try {
        return classLoader.loadClass(className);
      } catch (ClassNotFoundException ex) {
        // With additional class loaders, it is legitimate to ignore ClassNotFoundException
        if (classLoaders.isEmpty()) {
          logClassNotFoundError(className, ex);
        }
      }
    }
    if (RuntimeBehavior.shouldSkipUsingCallerClassLoader()) {
      return null;
    }

    try {
      return Class.forName(className);
    } catch (ClassNotFoundException cnfe) {
      logClassNotFoundError(className, cnfe);
      return null;
    }
  }

  private static void logClassNotFoundError(String className, Exception ex) {
    Utils.log(
        CLASS_HELPER,
        2,
        "Could not instantiate " + className + " : Class doesn't exist (" + ex.getMessage() + ")");
  }

  /**
   * For the given class, returns the method annotated with @Factory or null if none is found.
   * This method does not search up the superclass hierarchy. If more than one method is @Factory
   * annotated, a TestNGException is thrown.
   *
   * @param cls The class to search for the @Factory annotation.
   * @param finder The finder (JDK 1.4 or JDK 5.0+) use to search for the annotation.
   * @return the @Factory methods
   */
  public static List findDeclaredFactoryMethods(
      Class cls, IAnnotationFinder finder) {
    List result = new ArrayList<>();

    for (Method method : getAvailableMethods(cls)) {
      IFactoryAnnotation f = finder.findAnnotation(method, IFactoryAnnotation.class);
      if (f != null) {
        ConstructorOrMethod factory = new ConstructorOrMethod(method);
        factory.setEnabled(f.getEnabled());
        result.add(factory);
      }
    }

    for (Constructor constructor : cls.getDeclaredConstructors()) {
      IFactoryAnnotation f = finder.findAnnotation(constructor, IFactoryAnnotation.class);
      if (f != null) {
        ConstructorOrMethod factory = new ConstructorOrMethod(constructor);
        factory.setEnabled(f.getEnabled());
        result.add(factory);
      }
    }

    return result;
  }

  /**
   * Extract all callable methods of a class and all its super (keeping in mind the Java access
   * rules).
   */
  public static Set getAvailableMethods(Class clazz) {
    Map> methods = Maps.newHashMap();
    for (final Method declaredMethod : ReflectionHelper.getLocalMethods(clazz)) {
      appendMethod(methods, declaredMethod);
    }

    Class parent = clazz.getSuperclass();
    if (null != parent) {
      while (!Object.class.equals(parent)) {
        Set>> extractedMethods =
            extractMethods(clazz, parent, methods).entrySet();
        for (Map.Entry> extractedMethod : extractedMethods) {
          Set m = methods.get(extractedMethod.getKey());
          if (m == null) {
            methods.put(extractedMethod.getKey(), extractedMethod.getValue());
          } else {
            m.addAll(extractedMethod.getValue());
          }
        }
        parent = parent.getSuperclass();
      }
    }

    Set returnValue = Sets.newHashSet();
    for (Set each : methods.values()) {
      returnValue.addAll(each);
    }
    return returnValue;
  }

  public static IJUnitTestRunner createTestRunner(TestRunner runner) {
    IJUnitTestRunner tr = null;
    try {
      // try to get runner for JUnit 4 first
      Class.forName("org.junit.Test");
      Class clazz = ClassHelper.forName(JUNIT_4_TESTRUNNER);
      if (clazz != null) {
        tr = (IJUnitTestRunner) clazz.newInstance();
        tr.setTestResultNotifier(runner);
      }
    } catch (Throwable t) {
      Utils.log(CLASS_HELPER, 2, "JUnit 4 was not found on the classpath");
      try {
        // fallback to JUnit 3
        Class.forName("junit.framework.Test");
        Class clazz = ClassHelper.forName(JUNIT_TESTRUNNER);
        if (clazz != null) {
          tr = (IJUnitTestRunner) clazz.newInstance();
          tr.setTestResultNotifier(runner);
        }
      } catch (Exception ex) {
        Utils.log(CLASS_HELPER, 2, "JUnit 3 was not found on the classpath");
        // there's no JUnit on the classpath
        throw new TestNGException("Cannot create JUnit runner", ex);
      }
    }
    return tr;
  }

  private static void appendMethod(Map> methods, Method declaredMethod) {
    Set declaredMethods =
        methods.computeIfAbsent(declaredMethod.getName(), k -> Sets.newHashSet());
    declaredMethods.add(declaredMethod);
  }

  private static Map> extractMethods(
      Class childClass, Class clazz, Map> collected) {
    Map> methods = Maps.newHashMap();

    Method[] declaredMethods = clazz.getDeclaredMethods();

    Package childPackage = childClass.getPackage();
    Package classPackage = clazz.getPackage();
    boolean isSamePackage = isSamePackage(childPackage, classPackage);

    for (Method method : declaredMethods) {
      if (canInclude(isSamePackage, method, collected)) {
        appendMethod(methods, method);
      }
    }

    return methods;
  }

  private static boolean canInclude(
      boolean isSamePackage, Method method, Map> collected) {
    int methodModifiers = method.getModifiers();
    boolean visible =
        (Modifier.isPublic(methodModifiers) || Modifier.isProtected(methodModifiers))
            || (isSamePackage && !Modifier.isPrivate(methodModifiers));
    boolean hasNoInheritanceTraits =
        !isOverridden(method, collected) && !Modifier.isAbstract(methodModifiers);
    return visible && hasNoInheritanceTraits;
  }

  private static boolean isSamePackage(Package childPackage, Package classPackage) {
    boolean isSamePackage = false;

    if ((null == childPackage) && (null == classPackage)) {
      isSamePackage = true;
    }
    if ((null != childPackage) && (null != classPackage)) {
      isSamePackage = childPackage.getName().equals(classPackage.getName());
    }
    return isSamePackage;
  }

  private static boolean isOverridden(Method method, Map> methodsByName) {
    Set collectedMethods = methodsByName.get(method.getName());
    if (collectedMethods == null) {
      return false;
    }
    Class methodClass = method.getDeclaringClass();
    Class[] methodParams = method.getParameterTypes();

    for (Method m : collectedMethods) {
      Class[] paramTypes = m.getParameterTypes();
      if (methodClass.isAssignableFrom(m.getDeclaringClass())
          && methodParams.length == paramTypes.length) {
        boolean sameParameters = true;
        for (int i = 0; i < methodParams.length; i++) {
          if (!methodParams[i].equals(paramTypes[i])) {
            sameParameters = false;
            break;
          }
        }

        if (sameParameters) {
          return true;
        }
      }
    }

    return false;
  }

  public static IMethodSelector createSelector(org.testng.xml.XmlMethodSelector selector) {
    try {
      Class cls = Class.forName(selector.getClassName());
      return (IMethodSelector) cls.newInstance();
    } catch (Exception ex) {
      throw new TestNGException("Couldn't find method selector : " + selector.getClassName(), ex);
    }
  }

  /** Create an instance for the given class. */
  public static Object createInstance(
      Class declaringClass,
      Map, IClass> classes,
      XmlTest xmlTest,
      IAnnotationFinder finder,
      ITestObjectFactory objectFactory,
      boolean create
  ) {
    if (objectFactory instanceof IObjectFactory) {
      return createInstance1(
          declaringClass, classes, xmlTest, finder, (IObjectFactory) objectFactory, create);
    } else if (objectFactory instanceof IObjectFactory2) {
      return createInstance2(declaringClass, (IObjectFactory2) objectFactory);
    } else {
      throw new AssertionError("Unknown object factory type:" + objectFactory);
    }
  }

  private static Object createInstance2(Class declaringClass, IObjectFactory2 objectFactory) {
    return objectFactory.newInstance(declaringClass);
  }

  public static Object createInstance1(
      Class declaringClass,
      Map, IClass> classes,
      XmlTest xmlTest,
      IAnnotationFinder finder,
      IObjectFactory factory,
      boolean create
  ) {
    Object result = null;

    try {
      Constructor constructor = findAnnotatedConstructor(finder, declaringClass);
      if (null != constructor) {
      // Any annotated constructor?
        try {
          result = instantiateUsingParameterizedConstructor(finder, constructor, xmlTest, factory);
        } catch(IllegalArgumentException e) {
          return null;
        }
      } else {
        // No, just try to instantiate the parameterless constructor (or the one with a String)
        result = instantiateUsingDefaultConstructor(declaringClass, classes, xmlTest, factory);
      }
    } catch (TestNGException ex) {
      throw ex;
    } catch (NoSuchMethodException ex) {
      // Empty catch block
    } catch (Throwable cause) {
      // Something else went wrong when running the constructor
      throw new TestNGException(
          "An error occurred while instantiating class "
              + declaringClass.getName() + ": " + cause.getMessage(), cause);
    }

    if (result == null && create) {
      String suffix = "instantiated";
      if (!Modifier.isPublic(declaringClass.getModifiers())) {
        suffix += "/accessed.";
      }
      throw new TestNGException("An error occurred while instantiating class " + declaringClass.getName() + ". "
              + "Check to make sure it can be " + suffix);
    }

    return result;
  }

  private static Object instantiateUsingParameterizedConstructor(IAnnotationFinder finder,
      Constructor constructor, XmlTest xmlTest, IObjectFactory objectFactory) {
    IFactoryAnnotation factoryAnnotation = finder
        .findAnnotation(constructor, IFactoryAnnotation.class);
    if (factoryAnnotation != null) {
      throw new IllegalArgumentException("No factory annotation found.");
    }

    IParametersAnnotation parametersAnnotation =
        finder.findAnnotation(constructor, IParametersAnnotation.class);
    if (parametersAnnotation == null) {
      // null if the annotation is @Factory
      return null;
    }
    String[] parameterNames = parametersAnnotation.getValue();
    Object[] parameters =
        Parameters.createInstantiationParameters(
            constructor,
            "@Parameters",
            finder,
            parameterNames,
            xmlTest.getAllParameters(),
            xmlTest.getSuite());
    return objectFactory.newInstance(constructor, parameters);
  }


  private static Object instantiateUsingDefaultConstructor(Class declaringClass,
      Map, IClass> classes, XmlTest xmlTest, IObjectFactory factory)
      throws NoSuchMethodException, IllegalAccessException, InstantiationException {
    // If this class is a (non-static) nested class, the constructor contains a hidden
    // parameter of the type of the enclosing class
    Class[] parameterTypes = new Class[0];
    Object[] parameters = new Object[0];
    Class ec = declaringClass.getEnclosingClass();
    boolean isStatic = 0 != (declaringClass.getModifiers() & Modifier.STATIC);

    // Only add the extra parameter if the nested class is not static
    if ((null != ec) && !isStatic) {
      parameterTypes = new Class[] {ec};
      parameters = new Object[]{computeParameters(classes, ec, factory)};
    } // isStatic

    Constructor ct;
    try {
      ct = declaringClass.getDeclaredConstructor(parameterTypes);
    } catch (NoSuchMethodException ex) {
      ct = declaringClass.getDeclaredConstructor(String.class);
      parameters = new Object[]{xmlTest.getName()};
      // If ct == null here, we'll pass a null
      // constructor to the factory and hope it can deal with it
    }
    return factory.newInstance(ct, parameters);
  }

  private static Object computeParameters(Map, IClass> classes,
      Class ec, IObjectFactory factory)
      throws NoSuchMethodException, IllegalAccessException, InstantiationException {
    // Create an instance of the enclosing class so we can instantiate
    // the nested class (actually, we reuse the existing instance).
    IClass enclosingIClass = classes.get(ec);
    if (enclosingIClass == null) {
      return ec.newInstance();
    }
    Object[] enclosingInstances = enclosingIClass.getInstances(false);
    if (enclosingInstances == null || enclosingInstances.length == 0) {
      return factory.newInstance(ec.getConstructor(ec));
    }
    return enclosingInstances[0];
  }

  /** Find the best constructor given the parameters found on the annotation */
  private static Constructor findAnnotatedConstructor(
      IAnnotationFinder finder, Class declaringClass) {
    Constructor[] constructors = declaringClass.getDeclaredConstructors();

    for (Constructor result : constructors) {
      IParametersAnnotation parametersAnnotation =
          finder.findAnnotation(result, IParametersAnnotation.class);
      if (parametersAnnotation != null) {
        String[] parameters = parametersAnnotation.getValue();
        Class[] parameterTypes = result.getParameterTypes();
        if (parameters.length != parameterTypes.length) {
          throw new TestNGException(
              "Parameter count mismatch:  "
                  + result
                  + "\naccepts "
                  + parameterTypes.length
                  + " parameters but the @Test annotation declares "
                  + parameters.length);
        }
        return result;
      }

      IFactoryAnnotation factoryAnnotation =
          finder.findAnnotation(result, IFactoryAnnotation.class);
      if (factoryAnnotation != null) {
        return result;
      }
    }

    return null;
  }

  public static  T tryOtherConstructor(Class declaringClass) {
    T result;
    try {
      // Special case for inner classes
      if (declaringClass.getModifiers() == 0) {
        return null;
      }

      Constructor ctor = declaringClass.getConstructor(String.class);
      result = ctor.newInstance("Default test name");
    } catch (Exception e) {
      String message = e.getMessage();
      if ((message == null) && (e.getCause() != null)) {
        message = e.getCause().getMessage();
      }
      String error =
          "Could not create an instance of class "
              + declaringClass
              + ((message != null) ? (": " + message) : "")
              + ".\nPlease make sure it has a constructor that accepts either a String or no parameter.";
      throw new TestNGException(error);
    }

    return result;
  }

  /**
   * Returns the Class object corresponding to the given name. The name may be of the following
   * form:
   *
   * 
    *
  • A class name: "org.testng.TestNG" *
  • A class file name: "/testng/src/org/testng/TestNG.class" *
  • A class source name: "d:\testng\src\org\testng\TestNG.java" *
* * @param file the class name. * @return the class corresponding to the name specified. */ public static Class fileToClass(String file) { Class result = null; if (!file.endsWith(".class") && !file.endsWith(".java")) { // Doesn't end in .java or .class, assume it's a class name if (file.startsWith("class ")) { file = file.substring("class ".length()); } result = ClassHelper.forName(file); if (null == result) { throw new TestNGException("Cannot load class from file: " + file); } return result; } int classIndex = file.lastIndexOf(".class"); if (-1 == classIndex) { classIndex = file.lastIndexOf(".java"); } // Transforms the file name into a class name. // Remove the ".class" or ".java" extension. String shortFileName = file.substring(0, classIndex); // Split file name into segments. For example "c:/java/classes/com/foo/A" String[] segments = shortFileName.split("[/\\\\]", -1); // // Check if the last good root index works for this one. For example, if the previous // name was "c:/java/classes/com/foo/A.class" then lastGoodRootIndex is 3 and we // try to make a class name ignoring the first lastGoodRootIndex segments (3). This // will succeed rapidly if the path is the same as the one from the previous name. // if (-1 != lastGoodRootIndex) { StringBuilder className = new StringBuilder(segments[lastGoodRootIndex]); for (int i = lastGoodRootIndex + 1; i < segments.length; i++) { className.append(".").append(segments[i]); } result = ClassHelper.forName(className.toString()); if (null != result) { return result; } } // // We haven't found a good root yet, start by resolving the class from the end segment // and work our way up. For example, if we start with "c:/java/classes/com/foo/A" // we'll start by resolving "A", then "foo.A", then "com.foo.A" until something // resolves. When it does, we remember the path we are at as "lastGoodRoodIndex". // String className = ""; for (int i = segments.length - 1; i >= 0; i--) { if (className.length() == 0) { className = segments[i]; } else { className = segments[i] + "." + className; } result = ClassHelper.forName(className); if (null != result) { lastGoodRootIndex = i; break; } } if (null == result) { throw new TestNGException("Cannot load class from file: " + file); } return result; } /** * @param cls - The class to look for. * @param suite - The {@link XmlSuite} whose <test> tags needs to be searched in. * @return - All the {@link XmlClass} objects that share the same <test> tag as the class. */ public static XmlClass[] findClassesInSameTest(Class cls, XmlSuite suite) { Collection vResult = Sets.newHashSet(); for (XmlTest test : suite.getTests()) { vResult.addAll(findClassesInSameTest(cls, test)); } return vResult.toArray(new XmlClass[0]); } private static Collection findClassesInSameTest(Class cls, XmlTest xmlTest) { Collection vResult = Sets.newHashSet(); String className = cls.getName(); for (XmlClass testClass : xmlTest.getXmlClasses()) { if (testClass.getName().equals(className)) { // Found it, add all the classes in this test in the result vResult.addAll(xmlTest.getXmlClasses()); break; } } return vResult; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy