Home | History | Annotate | Download | only in xml
      1 package org.testng.xml;
      2 
      3 import static org.testng.internal.Utils.isStringBlank;
      4 
      5 import org.testng.ITestObjectFactory;
      6 import org.testng.TestNGException;
      7 import org.testng.collections.Lists;
      8 import org.testng.collections.Maps;
      9 import org.testng.internal.Utils;
     10 import org.testng.log4testng.Logger;
     11 import org.xml.sax.Attributes;
     12 import org.xml.sax.InputSource;
     13 import org.xml.sax.SAXException;
     14 import org.xml.sax.SAXParseException;
     15 import org.xml.sax.helpers.DefaultHandler;
     16 
     17 import java.io.IOException;
     18 import java.io.InputStream;
     19 import java.util.ArrayList;
     20 import java.util.List;
     21 import java.util.Map;
     22 import java.util.Stack;
     23 
     24 /**
     25  * Suite definition parser utility.
     26  *
     27  * @author Cedric Beust
     28  * @author <a href='mailto:the_mindstorm (at) evolva.ro'>Alexandru Popescu</a>
     29  */
     30 public 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