1package org.testng.xml;
2
3import static org.testng.internal.Utils.isStringBlank;
4
5import org.testng.ITestObjectFactory;
6import org.testng.TestNGException;
7import org.testng.collections.Lists;
8import org.testng.collections.Maps;
9import org.testng.internal.Utils;
10import org.testng.log4testng.Logger;
11import org.xml.sax.Attributes;
12import org.xml.sax.InputSource;
13import org.xml.sax.SAXException;
14import org.xml.sax.SAXParseException;
15import org.xml.sax.helpers.DefaultHandler;
16
17import java.io.IOException;
18import java.io.InputStream;
19import java.util.ArrayList;
20import java.util.List;
21import java.util.Map;
22import java.util.Stack;
23
24/**
25 * Suite definition parser utility.
26 *
27 * @author Cedric Beust
28 * @author <a href='mailto:the_mindstorm@evolva.ro'>Alexandru Popescu</a>
29 */
30public class TestNGContentHandler extends DefaultHandler {
31  private XmlSuite m_currentSuite = null;
32  private XmlTest m_currentTest = null;
33  private List<String> m_currentDefines = null;
34  private List<String> m_currentRuns = null;
35  private List<XmlClass> m_currentClasses = null;
36  private int m_currentTestIndex = 0;
37  private int m_currentClassIndex = 0;
38  private int m_currentIncludeIndex = 0;
39  private List<XmlPackage> m_currentPackages = null;
40  private XmlPackage m_currentPackage = null;
41  private List<XmlSuite> m_suites = Lists.newArrayList();
42  private List<String> m_currentIncludedGroups = null;
43  private List<String> m_currentExcludedGroups = null;
44  private Map<String, String> m_currentTestParameters = null;
45  private Map<String, String> m_currentSuiteParameters = null;
46  private Map<String, String> m_currentClassParameters = null;
47  private Include m_currentInclude;
48  private List<String> m_currentMetaGroup = null;
49  private String m_currentMetaGroupName;
50
51  enum Location {
52    SUITE,
53    TEST,
54    CLASS,
55    INCLUDE,
56    EXCLUDE
57  }
58  private Stack<Location> m_locations = new Stack<>();
59
60  private XmlClass m_currentClass = null;
61  private ArrayList<XmlInclude> m_currentIncludedMethods = null;
62  private List<String> m_currentExcludedMethods = null;
63  private ArrayList<XmlMethodSelector> m_currentSelectors = null;
64  private XmlMethodSelector m_currentSelector = null;
65  private String m_currentLanguage = null;
66  private String m_currentExpression = null;
67  private List<String> m_suiteFiles = Lists.newArrayList();
68  private boolean m_enabledTest;
69  private List<String> m_listeners;
70
71  private String m_fileName;
72  private boolean m_loadClasses;
73  private boolean m_validate = false;
74  private boolean m_hasWarn = false;
75
76  public TestNGContentHandler(String fileName, boolean loadClasses) {
77    m_fileName = fileName;
78    m_loadClasses = loadClasses;
79  }
80
81  static private void ppp(String s) {
82    System.out.println("[TestNGContentHandler] " + s);
83  }
84
85  /*
86   * (non-Javadoc)
87   *
88   * @see org.xml.sax.EntityResolver#resolveEntity(java.lang.String,
89   *      java.lang.String)
90   */
91  @Override
92  public InputSource resolveEntity(String systemId, String publicId)
93      throws IOException, SAXException {
94    InputSource result = null;
95    if (Parser.DEPRECATED_TESTNG_DTD_URL.equals(publicId)
96        || Parser.TESTNG_DTD_URL.equals(publicId)) {
97      m_validate = true;
98      InputStream is = getClass().getClassLoader().getResourceAsStream(Parser.TESTNG_DTD);
99      if (null == is) {
100        is = Thread.currentThread().getContextClassLoader().getResourceAsStream(Parser.TESTNG_DTD);
101        if (null == is) {
102          System.out.println("WARNING: couldn't find in classpath " + publicId
103              + "\n" + "Fetching it from the Web site.");
104          result = super.resolveEntity(systemId, publicId);
105        }
106        else {
107          result = new InputSource(is);
108        }
109      }
110      else {
111        result = new InputSource(is);
112      }
113    }
114    else {
115      result = super.resolveEntity(systemId, publicId);
116    }
117
118    return result;
119  }
120
121  /**
122   * Parse <suite-file>
123   */
124  private void xmlSuiteFile(boolean start, Attributes attributes) {
125    if (start) {
126      String path = attributes.getValue("path");
127      pushLocation(Location.SUITE);
128      m_suiteFiles.add(path);
129    }
130    else {
131      m_currentSuite.setSuiteFiles(m_suiteFiles);
132      popLocation(Location.SUITE);
133    }
134  }
135
136  /**
137   * Parse <suite>
138   */
139  private void xmlSuite(boolean start, Attributes attributes) {
140    if (start) {
141      pushLocation(Location.SUITE);
142      String name = attributes.getValue("name");
143      if (isStringBlank(name)) {
144        throw new TestNGException("The <suite> tag must define the name attribute");
145      }
146      m_currentSuite = new XmlSuite();
147      m_currentSuite.setFileName(m_fileName);
148      m_currentSuite.setName(name);
149      m_currentSuiteParameters = Maps.newHashMap();
150
151      String verbose = attributes.getValue("verbose");
152      if (null != verbose) {
153        m_currentSuite.setVerbose(Integer.parseInt(verbose));
154      }
155      String jUnit = attributes.getValue("junit");
156      if (null != jUnit) {
157        m_currentSuite.setJUnit(Boolean.valueOf(jUnit));
158      }
159      String parallel = attributes.getValue("parallel");
160      if (parallel != null) {
161        XmlSuite.ParallelMode mode = XmlSuite.ParallelMode.getValidParallel(parallel);
162        if (mode != null) {
163          m_currentSuite.setParallel(mode);
164        } else {
165          Utils.log("Parser", 1, "[WARN] Unknown value of attribute 'parallel' at suite level: '" + parallel + "'.");
166        }
167      }
168      String parentModule = attributes.getValue("parent-module");
169      if (parentModule != null) {
170        m_currentSuite.setParentModule(parentModule);
171      }
172      String guiceStage = attributes.getValue("guice-stage");
173      if (guiceStage != null) {
174        m_currentSuite.setGuiceStage(guiceStage);
175      }
176      String configFailurePolicy = attributes.getValue("configfailurepolicy");
177      if (null != configFailurePolicy) {
178        if (XmlSuite.SKIP.equals(configFailurePolicy) || XmlSuite.CONTINUE.equals(configFailurePolicy)) {
179          m_currentSuite.setConfigFailurePolicy(configFailurePolicy);
180        }
181      }
182      String groupByInstances = attributes.getValue("group-by-instances");
183      if (groupByInstances!= null) {
184        m_currentSuite.setGroupByInstances(Boolean.valueOf(groupByInstances));
185      }
186      String skip = attributes.getValue("skipfailedinvocationcounts");
187      if (skip != null) {
188        m_currentSuite.setSkipFailedInvocationCounts(Boolean.valueOf(skip));
189      }
190      String threadCount = attributes.getValue("thread-count");
191      if (null != threadCount) {
192        m_currentSuite.setThreadCount(Integer.parseInt(threadCount));
193      }
194      String dataProviderThreadCount = attributes.getValue("data-provider-thread-count");
195      if (null != dataProviderThreadCount) {
196        m_currentSuite.setDataProviderThreadCount(Integer.parseInt(dataProviderThreadCount));
197      }
198      String timeOut = attributes.getValue("time-out");
199      if (null != timeOut) {
200        m_currentSuite.setTimeOut(timeOut);
201      }
202      String objectFactory = attributes.getValue("object-factory");
203      if (null != objectFactory && m_loadClasses) {
204        try {
205          m_currentSuite.setObjectFactory((ITestObjectFactory)Class.forName(objectFactory).newInstance());
206        }
207        catch(Exception e) {
208          Utils.log("Parser", 1, "[ERROR] Unable to create custom object factory '" + objectFactory + "' :" + e);
209        }
210      }
211      String preserveOrder = attributes.getValue("preserve-order");
212      if (preserveOrder != null) {
213        m_currentSuite.setPreserveOrder(preserveOrder);
214      }
215      String allowReturnValues = attributes.getValue("allow-return-values");
216      if (allowReturnValues != null) {
217        m_currentSuite.setAllowReturnValues(Boolean.valueOf(allowReturnValues));
218      }
219    }
220    else {
221      m_currentSuite.setParameters(m_currentSuiteParameters);
222      m_suites.add(m_currentSuite);
223      m_currentSuiteParameters = null;
224      popLocation(Location.SUITE);
225    }
226  }
227
228  /**
229   * Parse <define>
230   */
231  private void xmlDefine(boolean start, Attributes attributes) {
232    if (start) {
233      String name = attributes.getValue("name");
234      m_currentDefines = Lists.newArrayList();
235      m_currentMetaGroup = Lists.newArrayList();
236      m_currentMetaGroupName = name;
237    }
238    else {
239      m_currentTest.addMetaGroup(m_currentMetaGroupName, m_currentMetaGroup);
240      m_currentDefines = null;
241    }
242  }
243
244  /**
245   * Parse <script>
246   */
247  private void xmlScript(boolean start, Attributes attributes) {
248    if (start) {
249//      ppp("OPEN SCRIPT");
250      m_currentLanguage = attributes.getValue("language");
251      m_currentExpression = "";
252    }
253    else {
254//      ppp("CLOSE SCRIPT:@@" + m_currentExpression + "@@");
255      m_currentSelector.setExpression(m_currentExpression);
256      m_currentSelector.setLanguage(m_currentLanguage);
257      if (m_locations.peek() == Location.TEST) {
258        m_currentTest.setBeanShellExpression(m_currentExpression);
259      }
260      m_currentLanguage = null;
261      m_currentExpression = null;
262    }
263  }
264
265  /**
266   * Parse <test>
267   */
268  private void xmlTest(boolean start, Attributes attributes) {
269    if (start) {
270      m_currentTest = new XmlTest(m_currentSuite, m_currentTestIndex++);
271      pushLocation(Location.TEST);
272      m_currentTestParameters = Maps.newHashMap();
273      final String testName= attributes.getValue("name");
274      if(isStringBlank(testName)) {
275        throw new TestNGException("The <test> tag must define the name attribute");
276      }
277      m_currentTest.setName(attributes.getValue("name"));
278      String verbose = attributes.getValue("verbose");
279      if (null != verbose) {
280        m_currentTest.setVerbose(Integer.parseInt(verbose));
281      }
282      String jUnit = attributes.getValue("junit");
283      if (null != jUnit) {
284        m_currentTest.setJUnit(Boolean.valueOf(jUnit));
285      }
286      String skip = attributes.getValue("skipfailedinvocationcounts");
287      if (skip != null) {
288        m_currentTest.setSkipFailedInvocationCounts(Boolean.valueOf(skip));
289      }
290      String groupByInstances = attributes.getValue("group-by-instances");
291      if (groupByInstances!= null) {
292        m_currentTest.setGroupByInstances(Boolean.valueOf(groupByInstances));
293      }
294      String preserveOrder = attributes.getValue("preserve-order");
295      if (preserveOrder != null) {
296        m_currentTest.setPreserveOrder(preserveOrder);
297      }
298      String parallel = attributes.getValue("parallel");
299      if (parallel != null) {
300        XmlSuite.ParallelMode mode = XmlSuite.ParallelMode.getValidParallel(parallel);
301        if (mode != null) {
302          m_currentTest.setParallel(mode);
303        } else {
304          Utils.log("Parser", 1, "[WARN] Unknown value of attribute 'parallel' for test '"
305            + m_currentTest.getName() + "': '" + parallel + "'");
306        }
307      }
308      String threadCount = attributes.getValue("thread-count");
309      if(null != threadCount) {
310        m_currentTest.setThreadCount(Integer.parseInt(threadCount));
311      }
312      String timeOut = attributes.getValue("time-out");
313      if (null != timeOut) {
314        m_currentTest.setTimeOut(Long.parseLong(timeOut));
315      }
316      m_enabledTest= true;
317      String enabledTestString = attributes.getValue("enabled");
318      if(null != enabledTestString) {
319        m_enabledTest = Boolean.valueOf(enabledTestString);
320      }
321    }
322    else {
323      if (null != m_currentTestParameters && m_currentTestParameters.size() > 0) {
324        m_currentTest.setParameters(m_currentTestParameters);
325      }
326      if (null != m_currentClasses) {
327        m_currentTest.setXmlClasses(m_currentClasses);
328      }
329      m_currentClasses = null;
330      m_currentTest = null;
331      m_currentTestParameters = null;
332      popLocation(Location.TEST);
333      if(!m_enabledTest) {
334        List<XmlTest> tests= m_currentSuite.getTests();
335        tests.remove(tests.size() - 1);
336      }
337    }
338  }
339
340  /**
341   * Parse <classes>
342   */
343  public void xmlClasses(boolean start, Attributes attributes) {
344    if (start) {
345      m_currentClasses = Lists.newArrayList();
346      m_currentClassIndex = 0;
347    }
348    else {
349      m_currentTest.setXmlClasses(m_currentClasses);
350      m_currentClasses = null;
351    }
352  }
353
354  /**
355   * Parse <listeners>
356   */
357  public void xmlListeners(boolean start, Attributes attributes) {
358    if (start) {
359      m_listeners = Lists.newArrayList();
360    }
361    else {
362      if (null != m_listeners) {
363        m_currentSuite.setListeners(m_listeners);
364        m_listeners = null;
365      }
366    }
367  }
368
369  /**
370   * Parse <listener>
371   */
372  public void xmlListener(boolean start, Attributes attributes) {
373    if (start) {
374      String listener = attributes.getValue("class-name");
375      m_listeners.add(listener);
376    }
377  }
378
379  /**
380   * Parse <packages>
381   */
382  public void xmlPackages(boolean start, Attributes attributes) {
383    if (start) {
384      m_currentPackages = Lists.newArrayList();
385    }
386    else {
387      if (null != m_currentPackages) {
388        switch(m_locations.peek()) {
389          case TEST:
390            m_currentTest.setXmlPackages(m_currentPackages);
391            break;
392          case SUITE:
393            m_currentSuite.setXmlPackages(m_currentPackages);
394            break;
395          case CLASS:
396            throw new UnsupportedOperationException("CLASS");
397        }
398      }
399
400      m_currentPackages = null;
401      m_currentPackage = null;
402    }
403  }
404
405  /**
406   * Parse <method-selectors>
407   */
408  public void xmlMethodSelectors(boolean start, Attributes attributes) {
409    if (start) {
410      m_currentSelectors = new ArrayList<>();
411    }
412    else {
413      switch(m_locations.peek()) {
414        case TEST:
415          m_currentTest.setMethodSelectors(m_currentSelectors);
416          break;
417        default:
418          m_currentSuite.setMethodSelectors(m_currentSelectors);
419          break;
420      }
421
422      m_currentSelectors = null;
423    }
424  }
425
426  /**
427   * Parse <selector-class>
428   */
429  public void xmlSelectorClass(boolean start, Attributes attributes) {
430    if (start) {
431      m_currentSelector.setName(attributes.getValue("name"));
432      String priority = attributes.getValue("priority");
433      if (priority == null) {
434        priority = "0";
435      }
436      m_currentSelector.setPriority(Integer.parseInt(priority));
437    }
438    else {
439      // do nothing
440    }
441  }
442
443  /**
444   * Parse <method-selector>
445   */
446  public void xmlMethodSelector(boolean start, Attributes attributes) {
447    if (start) {
448      m_currentSelector = new XmlMethodSelector();
449    }
450    else {
451      m_currentSelectors.add(m_currentSelector);
452      m_currentSelector = null;
453    }
454  }
455
456  private void xmlMethod(boolean start, Attributes attributes) {
457    if (start) {
458      m_currentIncludedMethods = new ArrayList<>();
459      m_currentExcludedMethods = Lists.newArrayList();
460      m_currentIncludeIndex = 0;
461    }
462    else {
463      m_currentClass.setIncludedMethods(m_currentIncludedMethods);
464      m_currentClass.setExcludedMethods(m_currentExcludedMethods);
465      m_currentIncludedMethods = null;
466      m_currentExcludedMethods = null;
467    }
468  }
469
470  /**
471   * Parse <run>
472   */
473  public void xmlRun(boolean start, Attributes attributes) throws SAXException {
474    if (start) {
475      m_currentRuns = Lists.newArrayList();
476    }
477    else {
478      if (m_currentTest != null) {
479        m_currentTest.setIncludedGroups(m_currentIncludedGroups);
480        m_currentTest.setExcludedGroups(m_currentExcludedGroups);
481      } else {
482        m_currentSuite.setIncludedGroups(m_currentIncludedGroups);
483        m_currentSuite.setExcludedGroups(m_currentExcludedGroups);
484      }
485      m_currentRuns = null;
486    }
487  }
488
489
490  /**
491   * Parse <group>
492   */
493  public void xmlGroup(boolean start, Attributes attributes) throws SAXException {
494    if (start) {
495      m_currentTest.addXmlDependencyGroup(attributes.getValue("name"),
496          attributes.getValue("depends-on"));
497    }
498  }
499
500  /**
501   * NOTE: I only invoke xml*methods (e.g. xmlSuite()) if I am acting on both
502   * the start and the end of the tag. This way I can keep the treatment of
503   * this tag in one place. If I am only doing something when the tag opens,
504   * the code is inlined below in the startElement() method.
505   */
506  @Override
507  public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
508    if (!m_validate && !m_hasWarn) {
509      Logger.getLogger(TestNGContentHandler.class).warn("It is strongly recommended to add " +
510              "\"<!DOCTYPE suite SYSTEM \"http://testng.org/testng-1.0.dtd\" >\" at the top of your file, " +
511              "otherwise TestNG may fail or not work as expected.");
512      m_hasWarn = true;
513    }
514    String name = attributes.getValue("name");
515
516    // ppp("START ELEMENT uri:" + uri + " sName:" + localName + " qName:" + qName +
517    // " " + attributes);
518    if ("suite".equals(qName)) {
519      xmlSuite(true, attributes);
520    }
521    else if ("suite-file".equals(qName)) {
522      xmlSuiteFile(true, attributes);
523    }
524    else if ("test".equals(qName)) {
525      xmlTest(true, attributes);
526    }
527    else if ("script".equals(qName)) {
528      xmlScript(true, attributes);
529    }
530    else if ("method-selector".equals(qName)) {
531      xmlMethodSelector(true, attributes);
532    }
533    else if ("method-selectors".equals(qName)) {
534      xmlMethodSelectors(true, attributes);
535    }
536    else if ("selector-class".equals(qName)) {
537      xmlSelectorClass(true, attributes);
538    }
539    else if ("classes".equals(qName)) {
540      xmlClasses(true, attributes);
541    }
542    else if ("packages".equals(qName)) {
543      xmlPackages(true, attributes);
544    }
545    else if ("listeners".equals(qName)) {
546      xmlListeners(true, attributes);
547    }
548    else if ("listener".equals(qName)) {
549      xmlListener(true, attributes);
550    }
551    else if ("class".equals(qName)) {
552      // If m_currentClasses is null, the XML is invalid and SAX
553      // will complain, but in the meantime, dodge the NPE so SAX
554      // can finish parsing the file.
555      if (null != m_currentClasses) {
556        m_currentClass = new XmlClass(name, m_currentClassIndex++, m_loadClasses);
557        m_currentClass.setXmlTest(m_currentTest);
558        m_currentClassParameters = Maps.newHashMap();
559        m_currentClasses.add(m_currentClass);
560        pushLocation(Location.CLASS);
561      }
562    }
563    else if ("package".equals(qName)) {
564      if (null != m_currentPackages) {
565        m_currentPackage = new XmlPackage();
566        m_currentPackage.setName(name);
567        m_currentPackages.add(m_currentPackage);
568      }
569    }
570    else if ("define".equals(qName)) {
571      xmlDefine(true, attributes);
572    }
573    else if ("run".equals(qName)) {
574      xmlRun(true, attributes);
575    }
576    else if ("group".equals(qName)) {
577      xmlGroup(true, attributes);
578    }
579    else if ("groups".equals(qName)) {
580      m_currentIncludedGroups = Lists.newArrayList();
581      m_currentExcludedGroups = Lists.newArrayList();
582    }
583    else if ("methods".equals(qName)) {
584      xmlMethod(true, attributes);
585    }
586    else if ("include".equals(qName)) {
587      xmlInclude(true, attributes);
588    }
589    else if ("exclude".equals(qName)) {
590      xmlExclude(true, attributes);
591    }
592    else if ("parameter".equals(qName)) {
593      String value = expandValue(attributes.getValue("value"));
594      switch(m_locations.peek()) {
595        case TEST:
596          m_currentTestParameters.put(name, value);
597          break;
598        case SUITE:
599          m_currentSuiteParameters.put(name, value);
600          break;
601        case CLASS:
602          m_currentClassParameters.put(name, value);
603          break;
604        case INCLUDE:
605          m_currentInclude.parameters.put(name, value);
606          break;
607      }
608    }
609  }
610
611  private static class Include {
612    String name;
613    String invocationNumbers;
614    String description;
615    Map<String, String> parameters = Maps.newHashMap();
616
617    public Include(String name, String numbers) {
618      this.name = name;
619      this.invocationNumbers = numbers;
620    }
621  }
622
623  private void xmlInclude(boolean start, Attributes attributes) {
624    if (start) {
625      m_locations.push(Location.INCLUDE);
626      m_currentInclude = new Include(attributes.getValue("name"),
627          attributes.getValue("invocation-numbers"));
628    } else {
629      String name = m_currentInclude.name;
630      if (null != m_currentIncludedMethods) {
631        String in = m_currentInclude.invocationNumbers;
632        XmlInclude include;
633        if (!Utils.isStringEmpty(in)) {
634          include = new XmlInclude(name, stringToList(in), m_currentIncludeIndex++);
635        } else {
636          include = new XmlInclude(name, m_currentIncludeIndex++);
637        }
638        for (Map.Entry<String, String> entry : m_currentInclude.parameters.entrySet()) {
639          include.addParameter(entry.getKey(), entry.getValue());
640        }
641
642        include.setDescription(m_currentInclude.description);
643        m_currentIncludedMethods.add(include);
644      }
645      else if (null != m_currentDefines) {
646        m_currentMetaGroup.add(name);
647      }
648      else if (null != m_currentRuns) {
649        m_currentIncludedGroups.add(name);
650      }
651      else if (null != m_currentPackage) {
652        m_currentPackage.getInclude().add(name);
653      }
654
655      popLocation(Location.INCLUDE);
656      m_currentInclude = null;
657    }
658  }
659
660  private void xmlExclude(boolean start, Attributes attributes) {
661    if (start) {
662      m_locations.push(Location.EXCLUDE);
663      String name = attributes.getValue("name");
664      if (null != m_currentExcludedMethods) {
665        m_currentExcludedMethods.add(name);
666      }
667      else if (null != m_currentRuns) {
668        m_currentExcludedGroups.add(name);
669      }
670      else if (null != m_currentPackage) {
671        m_currentPackage.getExclude().add(name);
672      }
673    } else {
674      popLocation(Location.EXCLUDE);
675    }
676  }
677
678  private void pushLocation(Location l) {
679    m_locations.push(l);
680  }
681
682  private Location popLocation(Location location) {
683    return m_locations.pop();
684  }
685
686  private List<Integer> stringToList(String in) {
687    String[] numbers = in.split(" ");
688    List<Integer> result = Lists.newArrayList();
689    for (String n : numbers) {
690      result.add(Integer.parseInt(n));
691    }
692    return result;
693  }
694
695  @Override
696  public void endElement(String uri, String localName, String qName) throws SAXException {
697    if ("suite".equals(qName)) {
698      xmlSuite(false, null);
699    }
700    else if ("suite-file".equals(qName)) {
701      xmlSuiteFile(false, null);
702    }
703    else if ("test".equals(qName)) {
704      xmlTest(false, null);
705    }
706    else if ("define".equals(qName)) {
707      xmlDefine(false, null);
708    }
709    else if ("run".equals(qName)) {
710      xmlRun(false, null);
711    }
712    else if ("methods".equals(qName)) {
713      xmlMethod(false, null);
714    }
715    else if ("classes".equals(qName)) {
716      xmlClasses(false, null);
717    }
718    else if ("packages".equals(qName)) {
719      xmlPackages(false, null);
720    }
721    else if ("class".equals(qName)) {
722      m_currentClass.setParameters(m_currentClassParameters);
723      m_currentClassParameters = null;
724      popLocation(Location.CLASS);
725    }
726    else if ("listeners".equals(qName)) {
727      xmlListeners(false, null);
728    }
729    else if ("method-selector".equals(qName)) {
730      xmlMethodSelector(false, null);
731    }
732    else if ("method-selectors".equals(qName)) {
733      xmlMethodSelectors(false, null);
734    }
735    else if ("selector-class".equals(qName)) {
736      xmlSelectorClass(false, null);
737    }
738    else if ("script".equals(qName)) {
739      xmlScript(false, null);
740    }
741    else if ("packages".equals(qName)) {
742      xmlPackages(false, null);
743    }
744    else if ("include".equals(qName)) {
745      xmlInclude(false, null);
746    } else if ("exclude".equals(qName)){
747      xmlExclude(false, null);
748    }
749  }
750
751  @Override
752  public void error(SAXParseException e) throws SAXException {
753    if (m_validate) {
754      throw e;
755    }
756  }
757
758  private boolean areWhiteSpaces(char[] ch, int start, int length) {
759    for (int i = start; i < start + length; i++) {
760      char c = ch[i];
761      if (c != '\n' && c != '\t' && c != ' ') {
762        return false;
763      }
764    }
765
766    return true;
767  }
768
769  @Override
770  public void characters(char ch[], int start, int length) {
771    if (null != m_currentLanguage && ! areWhiteSpaces(ch, start, length)) {
772      m_currentExpression += new String(ch, start, length);
773    }
774  }
775
776  public XmlSuite getSuite() {
777    return m_currentSuite;
778  }
779
780  private static String expandValue(String value)
781  {
782    StringBuffer result = null;
783    int startIndex = 0;
784    int endIndex = 0;
785    int startPosition = 0;
786    String property = null;
787    while ((startIndex = value.indexOf("${", startPosition)) > -1 && (endIndex = value.indexOf("}", startIndex + 3)) > -1) {
788      property = value.substring(startIndex + 2, endIndex);
789      if (result == null) {
790        result = new StringBuffer(value.substring(startPosition, startIndex));
791      } else {
792        result.append(value.substring(startPosition, startIndex));
793      }
794      String propertyValue = System.getProperty(property);
795      if (propertyValue == null) {
796        propertyValue = System.getenv(property);
797      }
798      if (propertyValue != null) {
799        result.append(propertyValue);
800      } else {
801        result.append("${");
802        result.append(property);
803        result.append("}");
804      }
805      startPosition = startIndex + 3 + property.length();
806    }
807    if (result != null) {
808      result.append(value.substring(startPosition));
809      return result.toString();
810    } else {
811      return value;
812    }
813  }
814}
815