1package org.testng.internal;
2
3import java.lang.reflect.Constructor;
4import java.lang.reflect.Method;
5import java.lang.reflect.Modifier;
6import java.util.List;
7import java.util.Map;
8import java.util.Set;
9
10import org.testng.IClass;
11import org.testng.IInstanceInfo;
12import org.testng.ITestContext;
13import org.testng.ITestObjectFactory;
14import org.testng.TestNGException;
15import org.testng.annotations.IAnnotation;
16import org.testng.collections.Lists;
17import org.testng.collections.Maps;
18import org.testng.internal.annotations.AnnotationHelper;
19import org.testng.internal.annotations.IAnnotationFinder;
20import org.testng.xml.XmlClass;
21import org.testng.xml.XmlTest;
22
23import static org.testng.internal.ClassHelper.getAvailableMethods;
24
25/**
26 * This class creates an ITestClass from a test class.
27 *
28 * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
29 */
30public class TestNGClassFinder extends BaseClassFinder {
31  private ITestContext m_testContext = null;
32  private Map<Class, List<Object>> m_instanceMap = Maps.newHashMap();
33
34  public TestNGClassFinder(ClassInfoMap cim,
35                           Map<Class, List<Object>> instanceMap,
36                           XmlTest xmlTest,
37                           IConfiguration configuration,
38                           ITestContext testContext)
39  {
40    m_testContext = testContext;
41
42    if(null == instanceMap) {
43      instanceMap= Maps.newHashMap();
44    }
45
46    IAnnotationFinder annotationFinder = configuration.getAnnotationFinder();
47    ITestObjectFactory objectFactory = configuration.getObjectFactory();
48
49    //
50    // Find all the new classes and their corresponding instances
51    //
52    Set<Class<?>> allClasses= cim.getClasses();
53
54    //very first pass is to find ObjectFactory, can't create anything else until then
55    if(objectFactory == null) {
56      objectFactory = new ObjectFactoryImpl();
57      outer:
58      for (Class cls : allClasses) {
59        try {
60          if (null != cls) {
61            Method[] ms;
62            try {
63              ms = cls.getMethods();
64            } catch (NoClassDefFoundError e) {
65              // https://github.com/cbeust/testng/issues/602
66              ppp("Warning: Can't link and determine methods of " + cls);
67              ms = new Method[0];
68            }
69            for (Method m : ms) {
70              IAnnotation a = annotationFinder.findAnnotation(m,
71                  org.testng.annotations.IObjectFactoryAnnotation.class);
72              if (null != a) {
73                if (!ITestObjectFactory.class.isAssignableFrom(m.getReturnType())) {
74                  throw new TestNGException("Return type of " + m + " is not IObjectFactory");
75                }
76                try {
77                  Object instance = cls.newInstance();
78                  if (m.getParameterTypes().length > 0 && m.getParameterTypes()[0].equals(ITestContext.class)) {
79                    objectFactory = (ITestObjectFactory) m.invoke(instance, testContext);
80                  } else {
81                    objectFactory = (ITestObjectFactory) m.invoke(instance);
82                  }
83                  break outer;
84                }
85                catch (Exception ex) {
86                  throw new TestNGException("Error creating object factory: " + cls,
87                      ex);
88                }
89              }
90            }
91          }
92        } catch (NoClassDefFoundError e) {
93          Utils.log("[TestNGClassFinder]", 1, "Unable to read methods on class " + cls.getName()
94              + " - unable to resolve class reference " + e.getMessage());
95
96          for (XmlClass xmlClass : xmlTest.getXmlClasses()) {
97            if (xmlClass.loadClasses() && xmlClass.getName().equals(cls.getName())) {
98              throw e;
99            }
100          }
101
102        }
103      }
104    }
105
106    for(Class cls : allClasses) {
107      if((null == cls)) {
108        ppp("FOUND NULL CLASS IN FOLLOWING ARRAY:");
109        int i= 0;
110        for(Class c : allClasses) {
111          ppp("  " + i + ": " + c);
112        }
113
114        continue;
115      }
116
117      if(isTestNGClass(cls, annotationFinder)) {
118        List allInstances= instanceMap.get(cls);
119        Object thisInstance= (null != allInstances) ? allInstances.get(0) : null;
120
121        // If annotation class and instances are abstract, skip them
122        if ((null == thisInstance) && Modifier.isAbstract(cls.getModifiers())) {
123          Utils.log("", 5, "[WARN] Found an abstract class with no valid instance attached: " + cls);
124          continue;
125        }
126
127        IClass ic= findOrCreateIClass(m_testContext, cls, cim.getXmlClass(cls), thisInstance,
128            xmlTest, annotationFinder, objectFactory);
129        if(null != ic) {
130          Object[] theseInstances = ic.getInstances(false);
131          if (theseInstances.length == 0) {
132            theseInstances = ic.getInstances(true);
133          }
134          Object instance= theseInstances[0];
135          putIClass(cls, ic);
136
137          ConstructorOrMethod factoryMethod =
138            ClassHelper.findDeclaredFactoryMethod(cls, annotationFinder);
139          if (factoryMethod != null && factoryMethod.getEnabled()) {
140            FactoryMethod fm = new FactoryMethod( /* cls, */
141              factoryMethod,
142              instance,
143              xmlTest,
144              annotationFinder,
145              m_testContext);
146            ClassInfoMap moreClasses = new ClassInfoMap();
147
148            {
149//            ppp("INVOKING FACTORY " + fm + " " + this.hashCode());
150              Object[] instances= fm.invoke();
151
152              //
153              // If the factory returned IInstanceInfo, get the class from it,
154              // otherwise, just call getClass() on the returned instances
155              //
156              if (instances.length > 0) {
157                if (instances[0] != null) {
158                  Class elementClass = instances[0].getClass();
159                  if(IInstanceInfo.class.isAssignableFrom(elementClass)) {
160                    for(Object o : instances) {
161                      IInstanceInfo ii = (IInstanceInfo) o;
162                      addInstance(ii.getInstanceClass(), ii.getInstance());
163                      moreClasses.addClass(ii.getInstanceClass());
164                    }
165                  }
166                  else {
167                    for (int i = 0; i < instances.length; i++) {
168                      Object o = instances[i];
169                      if (o == null) {
170                        throw new TestNGException("The factory " + fm + " returned a null instance" +
171                            "at index " + i);
172                      } else {
173                        addInstance(o.getClass(), o);
174                        if(!classExists(o.getClass())) {
175                          moreClasses.addClass(o.getClass());
176                        }
177                      }
178                    }
179                  }
180                }
181              }
182            }
183
184            if(moreClasses.getSize() > 0) {
185              TestNGClassFinder finder=
186                new TestNGClassFinder(moreClasses,
187                    m_instanceMap,
188                    xmlTest,
189                    configuration,
190                    m_testContext);
191
192              IClass[] moreIClasses= finder.findTestClasses();
193              for(IClass ic2 : moreIClasses) {
194                putIClass(ic2.getRealClass(), ic2);
195              }
196            } // if moreClasses.size() > 0
197          }
198        } // null != ic
199      } // if not TestNG class
200      else {
201        Utils.log("TestNGClassFinder", 3, "SKIPPING CLASS " + cls + " no TestNG annotations found");
202      }
203    } // for
204
205    //
206    // Add all the instances we found to their respective IClasses
207    //
208    for(Map.Entry<Class, List<Object>> entry : m_instanceMap.entrySet()) {
209      Class clazz = entry.getKey();
210      for(Object instance : entry.getValue()) {
211        IClass ic= getIClass(clazz);
212        if(null != ic) {
213          ic.addInstance(instance);
214        }
215      }
216    }
217  }
218
219  /**
220   * @return true if this class contains TestNG annotations (either on itself
221   * or on a superclass).
222   */
223  public static boolean isTestNGClass(Class<?> c, IAnnotationFinder annotationFinder) {
224    Class[] allAnnotations= AnnotationHelper.getAllAnnotations();
225    Class<?> cls = c;
226
227    try {
228      for(Class annotation : allAnnotations) {
229
230        for (cls = c; cls != null; cls = cls.getSuperclass()) {
231          // Try on the methods
232          for (Method m : getAvailableMethods(cls)) {
233            IAnnotation ma= annotationFinder.findAnnotation(m, annotation);
234            if(null != ma) {
235              return true;
236            }
237          }
238
239          // Try on the class
240          IAnnotation a= annotationFinder.findAnnotation(cls, annotation);
241          if(null != a) {
242            return true;
243          }
244
245          // Try on the constructors
246          for (Constructor ctor : cls.getConstructors()) {
247            IAnnotation ca= annotationFinder.findAnnotation(ctor, annotation);
248            if(null != ca) {
249              return true;
250            }
251          }
252        }
253      }
254
255      return false;
256
257    } catch (NoClassDefFoundError e) {
258      Utils.log("[TestNGClassFinder]", 1,
259          "Unable to read methods on class " + cls.getName()
260          + " - unable to resolve class reference " + e.getMessage());
261      return false;
262    }
263  }
264
265  private void addInstance(Class clazz, Object o) {
266    List<Object> list= m_instanceMap.get(clazz);
267
268    if(null == list) {
269      list= Lists.newArrayList();
270      m_instanceMap.put(clazz, list);
271    }
272
273    list.add(o);
274  }
275
276  public static void ppp(String s) {
277    System.out.println("[TestNGClassFinder] " + s);
278  }
279
280}
281