package org.testng.xml;
import javax.xml.parsers.ParserConfigurationException;
import org.testng.collections.Lists;
import org.testng.collections.Maps;
import org.xml.sax.SAXException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* <code>Parser</code> is a parser for a TestNG XML test suite file.
*/
public class Parser {
/** The name of the TestNG DTD. */
public static final String TESTNG_DTD = "testng-1.0.dtd";
/** The URL to the deprecated TestNG DTD. */
public static final String DEPRECATED_TESTNG_DTD_URL = "http://beust.com/testng/" + TESTNG_DTD;
/** The URL to the TestNG DTD. */
public static final String TESTNG_DTD_URL = "http://testng.org/" + TESTNG_DTD;
/** The default file name for the TestNG test suite if none is specified (testng.xml). */
public static final String DEFAULT_FILENAME = "testng.xml";
private static final IFileParser DEFAULT_FILE_PARSER = new XmlParser();
/** The file name of the xml suite being parsed. This may be null if the Parser
* has not been initialized with a file name. TODO CQ This member is never used. */
private String m_fileName;
private InputStream m_inputStream;
private IFileParser m_fileParser;
/**
* Constructs a <code>Parser</code> to use the inputStream as the source of
* the xml test suite to parse.
* @param filename the filename corresponding to the inputStream or null if
* unknown.
* @param inputStream the xml test suite input stream.
*/
public Parser(String fileName) {
init(fileName, null, null);
}
/**
* Creates a parser that will try to find the DEFAULT_FILENAME from the jar.
* @throws FileNotFoundException if the DEFAULT_FILENAME resource is not
* found in the classpath.
*/
public Parser() throws FileNotFoundException {
init(null, null, null);
}
public Parser(InputStream is) {
init(null, is, null);
}
private void init(String fileName, InputStream is, IFileParser fp) {
m_fileName = fileName != null ? fileName : DEFAULT_FILENAME;
m_inputStream = is;
m_fileParser = fp != null ? fp : DEFAULT_FILE_PARSER;
}
/**
* Returns an input stream on the resource named DEFAULT_FILENAME.
*
* @return an input stream on the resource named DEFAULT_FILENAME.
* @throws FileNotFoundException if the DEFAULT_FILENAME resource is not
* found in the classpath.
*/
// private static InputStream getInputStream(String fileName) throws FileNotFoundException {
// // Try to look for the DEFAULT_FILENAME from the jar
// ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// InputStream in;
// // TODO CQ is this OK? should we fall back to the default classloader if the
// // context classloader fails.
// if (classLoader != null) {
// in = classLoader.getResourceAsStream(fileName);
// }
// else {
// in = Parser.class.getResourceAsStream(fileName);
// }
// if (in == null) {
// throw new FileNotFoundException(fileName);
// }
// return in;
// }
/**
* Parses the TestNG test suite and returns the corresponding XmlSuite,
* and possibly, other XmlSuite that are pointed to by <suite-files>
* tags.
*
* @return the parsed TestNG test suite.
*
* @throws ParserConfigurationException
* @throws SAXException
* @throws IOException if an I/O error occurs while parsing the test suite file or
* if the default testng.xml file is not found.
*/
public Collection<XmlSuite> parse() throws ParserConfigurationException, SAXException, IOException
{
// Each suite found is put in this list, using their canonical
// path to make sure we don't add a same file twice
// (e.g. "testng.xml" and "./testng.xml")
List<String> processedSuites = Lists.newArrayList();
XmlSuite resultSuite = null;
File parentFile = null;
String mainFilePath = null;
if (m_fileName != null) {
File mainFile = new File(m_fileName);
mainFilePath = mainFile.getCanonicalPath();
parentFile = mainFile.getParentFile();
}
List<String> toBeParsed = Lists.newArrayList();
List<String> toBeAdded = Lists.newArrayList();
List<String> toBeRemoved = Lists.newArrayList();
toBeParsed.add(mainFilePath);
/*
* Keeps a track of parent XmlSuite for each child suite
*/
Map<String, XmlSuite> childToParentMap = Maps.newHashMap();
while (toBeParsed.size() > 0) {
for (String currentFile : toBeParsed) {
InputStream inputStream = m_inputStream != null
? m_inputStream
: new FileInputStream(currentFile);
XmlSuite result = m_fileParser.parse(currentFile, inputStream);
XmlSuite currentXmlSuite = result;
processedSuites.add(currentFile);
toBeRemoved.add(currentFile);
if (childToParentMap.containsKey(currentFile)) {
XmlSuite parentSuite = childToParentMap.get(currentFile);
//Set parent
currentXmlSuite.setParentSuite(parentSuite);
//append children
parentSuite.getChildSuites().add(currentXmlSuite);
}
if (null == resultSuite) {
resultSuite = currentXmlSuite;
}
List<String> suiteFiles = currentXmlSuite.getSuiteFiles();
if (suiteFiles.size() > 0) {
for (String path : suiteFiles) {
String canonicalPath;
if (parentFile != null && new File(parentFile, path).exists()) {
canonicalPath = new File(parentFile, path).getCanonicalPath();
} else {
canonicalPath = new File(path).getCanonicalPath();
}
if (!processedSuites.contains(canonicalPath)) {
toBeAdded.add(canonicalPath);
childToParentMap.put(canonicalPath, currentXmlSuite);
}
}
}
}
//
// Add and remove files from toBeParsed before we loop
//
for (String s : toBeRemoved) {
toBeParsed.remove(s);
}
toBeRemoved = Lists.newArrayList();
for (String s : toBeAdded) {
toBeParsed.add(s);
}
toBeAdded = Lists.newArrayList();
}
//returning a list of single suite to keep changes minimum
List<XmlSuite> resultList = Lists.newArrayList();
resultList.add(resultSuite);
return resultList;
}
public List<XmlSuite> parseToList()
throws ParserConfigurationException, SAXException, IOException
{
List<XmlSuite> result = Lists.newArrayList();
Collection<XmlSuite> suites = parse();
for (XmlSuite suite : suites) {
result.add(suite);
}
return result;
}
}