1package org.testng.xml.dom;
2
3import javax.xml.parsers.DocumentBuilder;
4import javax.xml.parsers.DocumentBuilderFactory;
5import javax.xml.parsers.ParserConfigurationException;
6import javax.xml.xpath.XPathExpressionException;
7
8import org.testng.Assert;
9import org.testng.collections.ListMultiMap;
10import org.testng.collections.Lists;
11import org.testng.collections.Maps;
12import org.testng.internal.collections.Pair;
13import org.testng.xml.XmlDefine;
14import org.testng.xml.XmlGroups;
15import org.testng.xml.XmlMethodSelector;
16import org.testng.xml.XmlSuite;
17import org.testng.xml.XmlTest;
18import org.w3c.dom.DOMException;
19import org.w3c.dom.Document;
20import org.w3c.dom.Element;
21import org.w3c.dom.Node;
22import org.w3c.dom.NodeList;
23import org.w3c.dom.Text;
24import org.xml.sax.SAXException;
25
26import java.io.File;
27import java.io.FileInputStream;
28import java.io.IOException;
29import java.lang.annotation.Annotation;
30import java.lang.reflect.InvocationTargetException;
31import java.lang.reflect.Method;
32import java.util.Arrays;
33import java.util.List;
34import java.util.Map;
35
36public class XDom {
37//  private static Map<String, Class<?>> m_map = Maps.newHashMap();
38  private Document m_document;
39  private ITagFactory m_tagFactory;
40
41  public XDom(ITagFactory tagFactory, Document document)
42      throws XPathExpressionException,
43      InstantiationException, IllegalAccessException {
44    m_tagFactory = tagFactory;
45    m_document = document;
46  }
47
48  public Object parse() throws XPathExpressionException,
49      InstantiationException, IllegalAccessException, SecurityException,
50      IllegalArgumentException, NoSuchMethodException,
51      InvocationTargetException {
52    Object result = null;
53    NodeList nodes = m_document.getChildNodes();
54    for (int i = 0; i < nodes.getLength(); i++) {
55      Node item = nodes.item(i);
56      if (item.getAttributes() != null) {
57        String nodeName = item.getNodeName();
58
59        System.out.println("Node name:" + nodeName);
60        Class<?> c = m_tagFactory.getClassForTag(nodeName);
61        if (c == null) {
62          throw new RuntimeException("No class found for tag " + nodeName);
63        }
64
65        result = c.newInstance();
66        populateAttributes(item, result);
67        if (ITagSetter.class.isAssignableFrom(result.getClass())) {
68          throw new RuntimeException("TAG SETTER");
69        }
70        populateChildren(item, result);
71      }
72    }
73    return result;
74  }
75
76  public void populateChildren(Node root, Object result) throws InstantiationException,
77      IllegalAccessException, XPathExpressionException, SecurityException, IllegalArgumentException, NoSuchMethodException, InvocationTargetException {
78    p("populateChildren: " + root.getLocalName());
79    NodeList childNodes = root.getChildNodes();
80    ListMultiMap<String, Object> children = Maps.newListMultiMap();
81    for (int i = 0; i < childNodes.getLength(); i++) {
82      Node item = childNodes.item(i);
83      if (item.getAttributes() != null) {
84        String nodeName = item.getNodeName();
85        if ("suite-files".equals(nodeName)) {
86          System.out.println("BREAK");
87        }
88
89        Class<?> c = m_tagFactory.getClassForTag(nodeName);
90        if (c == null) {
91          System.out.println("Warning: No class found for tag " + nodeName);
92          boolean foundSetter = invokeOnSetter(result, (Element) item, nodeName, null);
93          System.out.println("  found setter:" + foundSetter);
94        } else {
95          Object object = instantiateElement(c, result);
96          if (ITagSetter.class.isAssignableFrom(object.getClass())) {
97            System.out.println("Tag setter:"  + result);
98            ((ITagSetter) object).setProperty(nodeName, result, item);
99          } else {
100            children.put(nodeName, object);
101            populateAttributes(item, object);
102            populateContent(item, object);
103          }
104          boolean foundSetter = invokeOnSetter(result, (Element) item, nodeName, object);
105//          setProperty(result, nodeName, object);
106          populateChildren(item, object);
107        }
108
109//        boolean foundSetter = invokeOnSetter(result, (Element) item, nodeName);
110//        if (! foundSetter) {
111//          boolean foundListSetter = invokeOnListSetter(result, nodeName, item);
112//          if (! foundListSetter) {
113//          }
114//        }
115      }
116    }
117//    System.out.println("Found children:" + children);
118//    for (String s : children.getKeys()) {
119//      setCollectionProperty(result, s, children.get(s), object);
120//    }
121  }
122
123  /**
124   * Try to find a @ParentSetter. If this fails, try to find a constructor that takes the parent as a parameter.
125   * If this fails, use the default constructor.
126   */
127  private Object instantiateElement(Class<?> c, Object parent)
128      throws SecurityException, NoSuchMethodException,
129      IllegalArgumentException, InstantiationException, IllegalAccessException,
130      InvocationTargetException {
131    Object result = null;
132    Method m = findMethodAnnotatedWith(c, ParentSetter.class);
133    if (m != null) {
134        result = c.newInstance();
135    	m.invoke(result, parent);
136    } else {
137	    try {
138	      result = c.getConstructor(parent.getClass()).newInstance(parent);
139	    } catch(NoSuchMethodException ex) {
140	      result = c.newInstance();
141	    }
142    }
143
144    return result;
145  }
146
147//  private List<Pair<Method, ? extends Annotation>>
148//      findMethodsWithAnnotation(Class<?> c, Class<? extends Annotation> ac) {
149//    List<Pair<Method, ? extends Annotation>> result = Lists.newArrayList();
150//    for (Method m : c.getMethods()) {
151//      Annotation a = m.getAnnotation(ac);
152//      if (a != null) {
153//        result.add(Pair.of(m, a));
154//      }
155//    }
156//    return result;
157//  }
158
159  private Method findMethodAnnotatedWith(Class<?> c, Class<? extends Annotation> annotation) {
160	  for (Method m : c.getMethods()) {
161		  if (m.getAnnotation(annotation) != null) {
162			  return m;
163		  }
164	  }
165	  return null;
166  }
167
168private void populateContent(Node item, Object object) {
169    for (int i = 0; i < item.getChildNodes().getLength(); i++) {
170      Node child = item.getChildNodes().item(i);
171      if (child instanceof Text) {
172        setText(object, (Text) child);
173      }
174    }
175  }
176
177  private void setText(Object bean, Text child) {
178    List<Pair<Method, Wrapper>> pairs =
179        Reflect.findMethodsWithAnnotation(bean.getClass(), TagContent.class, bean);
180    for (Pair<Method, Wrapper> pair : pairs) {
181      try {
182        pair.first().invoke(bean, child.getTextContent());
183      } catch (IllegalArgumentException | InvocationTargetException | IllegalAccessException | DOMException e) {
184        e.printStackTrace();
185      }
186    }
187  }
188
189  private boolean invokeOnSetter(Object object, Element element, String nodeName,
190      Object bean) {
191    Pair<Method, Wrapper> pair =
192       Reflect.findSetterForTag(object.getClass(), nodeName, bean);
193
194    List<Object[]> allParameters = null;
195    if (pair != null) {
196      Method m = pair.first();
197      try {
198        if (pair.second() != null) {
199          allParameters = pair.second().getParameters(element);
200        } else {
201          allParameters = Lists.newArrayList();
202          allParameters.add(new Object[] { bean });
203        }
204
205        for (Object[] p : allParameters) {
206          m.invoke(object, p);
207        }
208        return true;
209      } catch (IllegalArgumentException e) {
210        System.out.println("Parameters: " + allParameters);
211        e.printStackTrace();
212      } catch (IllegalAccessException | InvocationTargetException e) {
213        e.printStackTrace();
214      }
215    }
216
217    return false;
218  }
219
220  private void populateAttributes(Node node, Object object) throws XPathExpressionException {
221    for (int j = 0; j < node.getAttributes().getLength(); j++) {
222      Node item = node.getAttributes().item(j);
223      setProperty(object, item.getLocalName(), item.getNodeValue());
224    }
225  }
226
227  private void setProperty(Object object, String name, Object value) {
228    Pair<Method, Wrapper> setter = Reflect.findSetterForTag(object.getClass(), name,
229        value);
230
231    if (setter != null) {
232      Method foundMethod = setter.first();
233      try {
234        Class<?> type = foundMethod.getParameterTypes()[0];
235        if (type == Boolean.class || type == boolean.class) {
236          foundMethod.invoke(object, Boolean.parseBoolean(value.toString()));
237        } else if (type == Integer.class || type == int.class) {
238          foundMethod.invoke(object, Integer.parseInt(value.toString()));
239        } else {
240          foundMethod.invoke(object, value);
241        }
242      } catch (IllegalArgumentException | InvocationTargetException | IllegalAccessException e) {
243        e.printStackTrace();
244      }
245    } else {
246      e("Couldn't find setter method for property" + name + " on " + object.getClass());
247    }
248  }
249
250//  private Method findSetter(Object object, String name) {
251//    String methodName = toCamelCaseSetter(name);
252//    Method foundMethod = null;
253//    for (Method m : object.getClass().getDeclaredMethods()) {
254//      if (m.getName().equals(methodName)) {
255//        foundMethod = m;
256//        break;
257//      }
258//    }
259//    return foundMethod;
260//  }
261
262  private void p(String string) {
263    System.out.println("[XDom] " + string);
264  }
265
266  private void e(String string) {
267    System.out.println("[XDom] [Error] " + string);
268  }
269
270  public static void main(String[] args) throws SAXException, IOException,
271      ParserConfigurationException, XPathExpressionException,
272      InstantiationException, IllegalAccessException, SecurityException,
273      IllegalArgumentException, NoSuchMethodException,
274      InvocationTargetException {
275    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
276    factory.setNamespaceAware(true); // never forget this!
277    DocumentBuilder builder = factory.newDocumentBuilder();
278    FileInputStream inputStream =
279        new FileInputStream(new File(System.getProperty("user.home")
280            + "/java/testng/src/test/resources/testng-all.xml"));
281    Document doc = builder.parse(inputStream);
282    XmlSuite result = (XmlSuite) new XDom(new TestNGTagFactory(), doc).parse();
283
284    test(result);
285    System.out.println(result.toXml());
286  }
287
288  private static void test(XmlSuite s) {
289    Assert.assertEquals("TestNG", s.getName());
290    Assert.assertEquals(s.getDataProviderThreadCount(), 3);
291    Assert.assertEquals(s.getThreadCount(), 2);
292
293    {
294      // method-selectors
295      List<XmlMethodSelector> selectors = s.getMethodSelectors();
296      Assert.assertEquals(selectors.size(), 2);
297      XmlMethodSelector s1 = selectors.get(0);
298      Assert.assertEquals(s1.getLanguage(), "javascript");
299      Assert.assertEquals(s1.getExpression(), "foo()");
300      XmlMethodSelector s2 = selectors.get(1);
301      Assert.assertEquals(s2.getClassName(), "SelectorClass");
302      Assert.assertEquals(s2.getPriority(), 3);
303    }
304
305    {
306      // child-suites
307      List<String> suiteFiles = s.getSuiteFiles();
308      Assert.assertEquals(suiteFiles, Arrays.asList("./junit-suite.xml"));
309    }
310
311    {
312      // parameters
313      Map<String, String> p = s.getParameters();
314      Assert.assertEquals(p.size(), 2);
315      Assert.assertEquals(p.get("suiteParameter"), "suiteParameterValue");
316      Assert.assertEquals(p.get("first-name"), "Cedric");
317    }
318
319    {
320      // run
321      Assert.assertEquals(s.getIncludedGroups(), Arrays.asList("includeThisGroup"));
322      Assert.assertEquals(s.getExcludedGroups(), Arrays.asList("excludeThisGroup"));
323      XmlGroups groups = s.getGroups();
324
325      // define
326      List<XmlDefine> defines = groups.getDefines();
327      Assert.assertEquals(defines.size(), 1);
328      XmlDefine define = defines.get(0);
329      Assert.assertEquals(define.getName(), "bigSuite");
330      Assert.assertEquals(define.getIncludes(), Arrays.asList("suite1", "suite2"));
331
332      // packages
333      Assert.assertEquals(s.getPackageNames(), Arrays.asList("com.example1", "com.example2"));
334
335      // listeners
336      Assert.assertEquals(s.getListeners(),
337          Arrays.asList("com.beust.Listener1", "com.beust.Listener2"));
338      // dependencies
339      // only defined on test for now
340    }
341
342    {
343      // tests
344      Assert.assertEquals(s.getTests().size(), 3);
345      for (int i = 0; i < s.getTests().size(); i++) {
346        if ("Nopackage".equals(s.getTests().get(i).getName())) {
347          testNoPackage(s.getTests().get(i));
348        }
349      }
350    }
351  }
352
353  private static void testNoPackage(XmlTest t) {
354    Assert.assertEquals(t.getThreadCount(), 42);
355    Assert.assertTrue(t.getAllowReturnValues());
356
357    // define
358    Map<String, List<String>> metaGroups = t.getMetaGroups();
359    Assert.assertEquals(metaGroups.get("evenodd"), Arrays.asList("even", "odd"));
360
361    // run
362    Assert.assertEquals(t.getIncludedGroups(), Arrays.asList("nopackage", "includeThisGroup"));
363    Assert.assertEquals(t.getExcludedGroups(), Arrays.asList("excludeThisGroup"));
364
365    // dependencies
366    Map<String, String> dg = t.getXmlDependencyGroups();
367    Assert.assertEquals(dg.size(), 2);
368    Assert.assertEquals(dg.get("e"), "f");
369    Assert.assertEquals(dg.get("g"), "h");
370  }
371}
372