/*******************************************************************************
* Copyright Technophobia Ltd 2012
*
* This file is part of the Substeps Eclipse Plugin.
*
* The Substeps Eclipse Plugin is free software: you can redistribute it and/or modify
* it under the terms of the Eclipse Public License v1.0.
*
* The Substeps Eclipse Plugin is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Eclipse Public License for more details.
*
* You should have received a copy of the Eclipse Public License
* along with the Substeps Eclipse Plugin. If not, see <http://www.eclipse.org/legal/epl-v10.html>.
******************************************************************************/
package com.technophobia.substeps.model.serialize;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.Locale;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jdt.core.IJavaProject;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.DTDHandler;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.AttributesImpl;
import com.technophobia.substeps.junit.ui.SubstepsRunSession;
import com.technophobia.substeps.model.structure.FailureTrace;
import com.technophobia.substeps.model.structure.Result;
import com.technophobia.substeps.model.structure.SubstepsTestElement;
import com.technophobia.substeps.model.structure.SubstepsTestLeafElement;
import com.technophobia.substeps.model.structure.SubstepsTestParentElement;
import com.technophobia.substeps.model.structure.SubstepsTestRootElement;
public class SubstepsRunSessionSerializer implements XMLReader {
private static final String EMPTY = ""; //$NON-NLS-1$
private static final String CDATA = "CDATA"; //$NON-NLS-1$
private static final Attributes NO_ATTS = new AttributesImpl();
private final SubstepsRunSession substepsRunSession;
private ContentHandler handler;
private ErrorHandler errorHandler;
private final NumberFormat timeFormat = new DecimalFormat("0.0##", new DecimalFormatSymbols(Locale.US)); //$NON-NLS-1$ // not localized, parseable by Double.parseDouble(..)
/**
* @param substepsRunSession
* the test run session to serialize
*/
public SubstepsRunSessionSerializer(final SubstepsRunSession substepsRunSession) {
Assert.isNotNull(substepsRunSession);
this.substepsRunSession = substepsRunSession;
}
@Override
public void parse(final InputSource input) throws IOException, SAXException {
if (handler == null)
throw new SAXException("ContentHandler missing"); //$NON-NLS-1$
handler.startDocument();
handleTestRun();
handler.endDocument();
}
private void handleTestRun() throws SAXException {
final AttributesImpl atts = new AttributesImpl();
addCDATA(atts, IXMLTags.ATTR_NAME, substepsRunSession.getTestRunName());
final IJavaProject project = substepsRunSession.getLaunchedProject();
if (project != null)
addCDATA(atts, IXMLTags.ATTR_PROJECT, project.getElementName());
addCDATA(atts, IXMLTags.ATTR_TESTS, substepsRunSession.getTotalCount());
addCDATA(atts, IXMLTags.ATTR_STARTED, substepsRunSession.getStartedCount());
addCDATA(atts, IXMLTags.ATTR_FAILURES, substepsRunSession.getFailureCount());
addCDATA(atts, IXMLTags.ATTR_ERRORS, substepsRunSession.getErrorCount());
addCDATA(atts, IXMLTags.ATTR_IGNORED, substepsRunSession.getIgnoredCount());
startElement(IXMLTags.NODE_TESTRUN, atts);
final SubstepsTestRootElement root = substepsRunSession.getTestRoot();
final SubstepsTestElement[] topSuites = root.getChildren();
for (int i = 0; i < topSuites.length; i++) {
handleTestElement(topSuites[i]);
}
endElement(IXMLTags.NODE_TESTRUN);
}
private void handleTestElement(final SubstepsTestElement testElement) throws SAXException {
if (testElement instanceof SubstepsTestParentElement) {
final SubstepsTestParentElement parentElement = (SubstepsTestParentElement) testElement;
final AttributesImpl atts = new AttributesImpl();
addCDATA(atts, IXMLTags.ATTR_NAME, parentElement.getTestName());
if (!Double.isNaN(parentElement.getElapsedTimeInSeconds()))
addCDATA(atts, IXMLTags.ATTR_TIME, timeFormat.format(parentElement.getElapsedTimeInSeconds()));
if (!testElement.getStatus().isComplete() || !testElement.getTestResult(false).equals(Result.UNDEFINED))
addCDATA(atts, IXMLTags.ATTR_INCOMPLETE, Boolean.TRUE.toString());
startElement(IXMLTags.NODE_TESTSUITE, atts);
addFailure(testElement);
final SubstepsTestElement[] children = parentElement.getChildren();
for (int i = 0; i < children.length; i++) {
handleTestElement(children[i]);
}
endElement(IXMLTags.NODE_TESTSUITE);
} else if (testElement instanceof SubstepsTestLeafElement) {
final SubstepsTestLeafElement leafElement = (SubstepsTestLeafElement) testElement;
final AttributesImpl atts = new AttributesImpl();
addCDATA(atts, IXMLTags.ATTR_NAME, leafElement.getTestMethodName());
// addCDATA(atts, IXMLTags.ATTR_CLASSNAME,
// leafElement.getClassName());
if (!Double.isNaN(leafElement.getElapsedTimeInSeconds()))
addCDATA(atts, IXMLTags.ATTR_TIME, timeFormat.format(leafElement.getElapsedTimeInSeconds()));
if (!testElement.getStatus().isComplete())
addCDATA(atts, IXMLTags.ATTR_INCOMPLETE, Boolean.TRUE.toString());
if (leafElement.isIgnored())
addCDATA(atts, IXMLTags.ATTR_IGNORED, Boolean.TRUE.toString());
startElement(IXMLTags.NODE_TESTCASE, atts);
addFailure(testElement);
endElement(IXMLTags.NODE_TESTCASE);
} else {
throw new IllegalStateException(String.valueOf(testElement));
}
}
private void addFailure(final SubstepsTestElement testElement) throws SAXException {
final FailureTrace failureTrace = testElement.getFailureTrace();
if (failureTrace != null) {
final AttributesImpl failureAtts = new AttributesImpl();
// addCDATA(failureAtts, IXMLTags.ATTR_MESSAGE, xx);
// addCDATA(failureAtts, IXMLTags.ATTR_TYPE, xx);
final String failureKind = testElement.getTestResult(false) == Result.ERROR ? IXMLTags.NODE_ERROR
: IXMLTags.NODE_FAILURE;
startElement(failureKind, failureAtts);
final String expected = failureTrace.getExpected();
final String actual = failureTrace.getActual();
if (expected != null) {
startElement(IXMLTags.NODE_EXPECTED, NO_ATTS);
addCharacters(expected);
endElement(IXMLTags.NODE_EXPECTED);
}
if (actual != null) {
startElement(IXMLTags.NODE_ACTUAL, NO_ATTS);
addCharacters(actual);
endElement(IXMLTags.NODE_ACTUAL);
}
final String trace = failureTrace.getTrace();
addCharacters(trace);
endElement(failureKind);
}
}
private void startElement(final String name, final Attributes atts) throws SAXException {
handler.startElement(EMPTY, name, name, atts);
}
private void endElement(final String name) throws SAXException {
handler.endElement(EMPTY, name, name);
}
private static void addCDATA(final AttributesImpl atts, final String name, final int value) {
addCDATA(atts, name, Integer.toString(value));
}
private static void addCDATA(final AttributesImpl atts, final String name, final String value) {
atts.addAttribute(EMPTY, EMPTY, name, CDATA, value);
}
private void addCharacters(final String string) throws SAXException {
final String escapedString = escapeNonUnicodeChars(string);
handler.characters(escapedString.toCharArray(), 0, escapedString.length());
}
/**
* Replaces all non-Unicode characters in the given string.
*
* @param string
* a string
* @return string with Java-escapes
* @since 3.6
*/
private static String escapeNonUnicodeChars(final String string) {
StringBuffer buf = null;
for (int i = 0; i < string.length(); i++) {
final char ch = string.charAt(i);
if (!(ch == 9 || ch == 10 || ch == 13 || ch >= 32)) {
if (buf == null) {
buf = new StringBuffer(string.substring(0, i));
}
buf.append("\\u"); //$NON-NLS-1$
final String hex = Integer.toHexString(ch);
for (int j = hex.length(); j < 4; j++)
buf.append('0');
buf.append(hex);
} else if (buf != null) {
buf.append(ch);
}
}
if (buf != null) {
return buf.toString();
}
return string;
}
@Override
public void setContentHandler(final ContentHandler handler) {
this.handler = handler;
}
@Override
public ContentHandler getContentHandler() {
return handler;
}
@Override
public void setErrorHandler(final ErrorHandler handler) {
errorHandler = handler;
}
@Override
public ErrorHandler getErrorHandler() {
return errorHandler;
}
// ignored:
@Override
public void parse(final String systemId) throws IOException, SAXException {
// No-op
}
@Override
public void setDTDHandler(final DTDHandler handler) {
// No-op
}
@Override
public DTDHandler getDTDHandler() {
return null;
}
@Override
public void setEntityResolver(final EntityResolver resolver) {
// No-op
}
@Override
public EntityResolver getEntityResolver() {
return null;
}
@Override
public void setProperty(final java.lang.String name, final java.lang.Object value) {
// No-op
}
@Override
public Object getProperty(final java.lang.String name) {
return null;
}
@Override
public void setFeature(final java.lang.String name, final boolean value) {
// No-op
}
@Override
public boolean getFeature(final java.lang.String name) {
return false;
}
}