JsonOutput.java

package org.jbehave.core.reporters;

import static org.jbehave.core.reporters.PrintStreamOutput.Format.JSON;

import java.io.PrintStream;
import java.util.Properties;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.jbehave.core.configuration.Keywords;

/**
 * <p>
 * Story reporter that outputs to a PrintStream, as JSON. It extends
 * {@link PrintStreamOutput}, providing JSON-based default output
 * patterns, which can be overridden via the {@link
 * JsonOutput (PrintStream,Properties)} constructor.
 * </p>
 *
 * @author Valery Yatsynovich
 */
public class JsonOutput extends PrintStreamOutput {

    private static final char JSON_DOCUMENT_START = 0;
    private static final char JSON_OBJECT_START = '{';
    private static final char JSON_ARRAY_START = '[';
    private static final char[] JSON_START_CHARS = { JSON_DOCUMENT_START, JSON_OBJECT_START, JSON_ARRAY_START };

    private static final String[] STEP_KEYS = { "successful", "ignorable", "comment", "pending", "notPerformed",
        "failed", "restarted" };

    private char lastChar = JSON_DOCUMENT_START;

    private int givenStoriesLevel = 0;
    private int storyPublishingLevel = 0;
    private boolean stepPublishing = false;

    public JsonOutput(PrintStream output, Keywords keywords) {
        this(output, new Properties(), keywords);
    }

    public JsonOutput(PrintStream output, Properties outputPatterns, Keywords keywords) {
        super(JSON, output, defaultXmlPatterns(), outputPatterns, keywords);
    }

    @Override
    protected void print(PrintStream output, String text) {
        if (!text.isEmpty()) {
            boolean doNotAddComma =
                    ArrayUtils.contains(JSON_START_CHARS, lastChar) || StringUtils.startsWithAny(text, "}", "]", ",");
            super.print(output, doNotAddComma ? text : "," + text);
            lastChar = text.charAt(text.length() - 1);
        }
    }

    @Override
    protected String format(String key, String defaultPattern, Object... args) {
        if ("beforeGivenStories".equals(key)) {
            givenStoriesLevel++;
        } else if ("afterGivenStories".equals(key)) {
            if (storyPublishingLevel == givenStoriesLevel) {
                // Closing given "stories"
                print("]");
                storyPublishingLevel--;
            }
            givenStoriesLevel--;
            return super.format(key, defaultPattern, args);
        } else if ("beforeStory".equals(key) && storyPublishingLevel < givenStoriesLevel) {
            // Starting given "stories"
            print("\"stories\": [");
            storyPublishingLevel++;
        }
        //Closing "examples" if "steps" are empty
        if ("exampleScenariosEnd".equals(key) && !stepPublishing) {
            print("}");
        }

        if (key.startsWith("afterBefore")) {
            stepPublishing = false;
        }

        if (stepPublishing) {
            if ("exampleScenariosEnd".equals(key) || "example".equals(key)) {
                // Closing previous "example"
                print("}");
            }
            if ("afterScenario".equals(key) || "afterScenarioWithFailure".equals(key)) {
                stepPublishing = false;
            }
        } else if (ArrayUtils.contains(STEP_KEYS, key)) {
            stepPublishing = true;
        }
        return super.format(key, defaultPattern, args);
    }

    @Override
    protected String transformPrintingTable(String text, String tableStart, String tableEnd) {
        return text;
    }

    @SuppressWarnings("checkstyle:LineLength")
    private static Properties defaultXmlPatterns() {
        Properties patterns = new Properties();
        patterns.setProperty("dryRun", "\"dryRun\": \"{0}\"");
        patterns.setProperty("beforeStory", "'{'\"id\": \"{0}\", \"path\": \"{2}\", \"title\": \"{1}\"");
        patterns.setProperty("storyCancelled", "'{'\"cancelled\": '{'\"keyword\": \"{0}\", \"durationKeyword\": \"{1}\", \"durationInSecs\": \"{2}\"}}");
        patterns.setProperty("afterStory", "}");
        patterns.setProperty("beforeScenarios", "\"scenarios\": [");
        patterns.setProperty("afterScenarios", "]");
        patterns.setProperty("afterStory", "}");
        patterns.setProperty("metaStart", "\"meta\": '['");
        patterns.setProperty("metaProperty", "'{'\"keyword\": \"{0}\", \"name\": \"{1}\", \"value\": \"{2}\"}");
        patterns.setProperty("metaEnd", "']'");
        patterns.setProperty("filter", "\"filter\": \"{0}\"");
        patterns.setProperty("narrative", "\"narrative\": '{'\"keyword\": \"{0}\",  \"inOrderTo\": '{'\"keyword\": \"{1}\", \"value\": \"{2}\"}, \"asA\": '{'\"keyword\": \"{3}\", \"value\": \"{4}\"}, \"iWantTo\": '{'\"keyword\": \"{5}\", \"value\": \"{6}\"}}");
        patterns.setProperty("lifecycleStart", "\"lifecycle\": '{'\"keyword\": \"{0}\"");
        patterns.setProperty("lifecycleEnd", "}");
        patterns.setProperty("lifecycleBeforeStart", "\"before\": '{'\"keyword\": \"{0}\", \"scopes\": [");
        patterns.setProperty("lifecycleBeforeEnd", "]}");
        patterns.setProperty("lifecycleAfterStart", "\"after\": '{'\"keyword\": \"{0}\", \"scopes\": [");
        patterns.setProperty("lifecycleAfterEnd", "]}");
        patterns.setProperty("lifecycleBeforeScopeStart", "'{'\"keyword\": \"{0}\", \"value\": \"{1}\", \"steps\": [");
        patterns.setProperty("lifecycleBeforeScopeEnd", "]}");
        patterns.setProperty("lifecycleAfterScopeStart", "'{'\"keyword\": \"{0}\", \"value\": \"{1}\", \"outcomes\": [");
        patterns.setProperty("lifecycleAfterScopeEnd", "]}");
        patterns.setProperty("lifecycleOutcomeStart", "'{'\"keyword\": \"{0}\", \"value\": \"{1}\", \"steps\": [");
        patterns.setProperty("lifecycleOutcomeEnd", "]}");
        patterns.setProperty("lifecycleMetaFilter", "\"metaFilter\": \"{0} {1}\"");
        patterns.setProperty("lifecycleStep", "\"{0}\"");
        patterns.setProperty("beforeBeforeStoriesSteps", "'{'\"beforeStoriesSteps\": [");
        patterns.setProperty("afterBeforeStoriesSteps", "]}");
        patterns.setProperty("beforeAfterStoriesSteps", "'{'\"afterStoriesSteps\": [");
        patterns.setProperty("afterAfterStoriesSteps", "]}");
        patterns.setProperty("beforeBeforeSystemStorySteps", "\"beforeSystemStorySteps\": [");
        patterns.setProperty("afterBeforeSystemStorySteps", "]");
        patterns.setProperty("beforeAfterSystemStorySteps", "\"afterSystemStorySteps\": [");
        patterns.setProperty("afterAfterSystemStorySteps", "]");
        patterns.setProperty("beforeBeforeUserStorySteps", "\"beforeUserStorySteps\": [");
        patterns.setProperty("afterBeforeUserStorySteps", "]");
        patterns.setProperty("beforeAfterUserStorySteps", "\"afterUserStorySteps\": [");
        patterns.setProperty("afterAfterUserStorySteps", "]");
        patterns.setProperty("beforeBeforeSystemScenarioSteps", "\"beforeSystemScenarioSteps\": [");
        patterns.setProperty("afterBeforeSystemScenarioSteps", "]");
        patterns.setProperty("beforeAfterSystemScenarioSteps", "\"afterSystemScenarioSteps\": [");
        patterns.setProperty("afterAfterSystemScenarioSteps", "]");
        patterns.setProperty("beforeBeforeUserScenarioSteps", "\"beforeUserScenarioSteps\": [");
        patterns.setProperty("afterBeforeUserScenarioSteps", "]");
        patterns.setProperty("beforeAfterUserScenarioSteps", "\"afterUserScenarioSteps\": [");
        patterns.setProperty("afterAfterUserScenarioSteps", "]");
        patterns.setProperty("beforeScenarioSteps", "\"steps\": [");
        patterns.setProperty("afterScenarioSteps", "]");
        patterns.setProperty("beforeScenario","'{'\"keyword\": \"{1}\", \"id\": \"{0}\", \"title\": \"{2}\"");
        patterns.setProperty("scenarioExcluded", "\"excluded\": '{'\"pattern\": \"{0}\"}");
        patterns.setProperty("afterScenario", "}");
        patterns.setProperty("afterScenarioWithFailure", "\"failure\": \"{0}\" }");
        patterns.setProperty("givenStories", "\"givenStories\": '{'\"keyword\": \"{0}\", \"paths\": \"{1}\"}");
        patterns.setProperty("beforeGivenStories", "\"givenStories\": '{'");
        patterns.setProperty("givenStoriesStart", "\"keyword\": \"{0}\", \"givenStories\":[");
        patterns.setProperty("givenStory", "'{'\"parameters\": \"{1}\", \"path\": \"{0}\"}");
        patterns.setProperty("givenStoriesEnd", "]");
        patterns.setProperty("afterGivenStories", "}");
        patterns.setProperty("beforeStep", "'{'");
        patterns.setProperty("successful", "\"outcome\": \"successful\", \"value\": \"{0}\"}");
        patterns.setProperty("ignorable", "\"outcome\": \"ignorable\", \"value\": \"{0}\"}");
        patterns.setProperty("comment", "\"outcome\": \"comment\", \"value\": \"{0}\"}");
        patterns.setProperty("pending", "\"outcome\": \"pending\", \"keyword\": \"{1}\", \"value\": \"{0}\", \"pendingMethod\": \"{2}\"}");
        patterns.setProperty("notPerformed", "\"outcome\": \"notPerformed\", \"keyword\": \"{1}\", \"value\": \"{0}\"}");
        patterns.setProperty("failed", "\"outcome\": \"failed\", \"keyword\": \"{1}\", \"value\": \"{0}\", \"failure\": \"{2}\"}");
        patterns.setProperty("restarted", "\"outcome\": \"restarted\", \"value\": \"{0}\", \"reason\": \"{1}\"}");
        patterns.setProperty("restartedStory", "'{'\"story\": '{'\"outcome\": \"restartedStory\", \"value\": \"{0}\", \"reason\": \"{1}\"}}");
        patterns.setProperty("outcomesTableStart", "'{'\"outcomes\": '{'");
        patterns.setProperty("outcomesTableHeadStart", "\"fields\": [");
        patterns.setProperty("outcomesTableHeadCell", "\"{0}\"");
        patterns.setProperty("outcomesTableHeadEnd", "]");
        patterns.setProperty("outcomesTableBodyStart", "\"values\": [");
        patterns.setProperty("outcomesTableRowStart", "[");
        patterns.setProperty("outcomesTableCell", "\"{0}\"");
        patterns.setProperty("outcomesTableRowEnd", "]");
        patterns.setProperty("outcomesTableBodyEnd", "]");
        patterns.setProperty("outcomesTableEnd", "}}");
        patterns.setProperty("beforeExampleParameters", " \"parameters\": '{'");
        patterns.setProperty("afterExampleParameters", "}");
        patterns.setProperty("exampleParameter", "\"{0}\":\"{1}\"");
        patterns.setProperty("numericParameter", "\"{0}\":{1}");
        patterns.setProperty("beforeExamples", "\"examples\": '{'\"keyword\": \"{0}\"");
        patterns.setProperty("examplesStepsStart", "\"steps\": [");
        patterns.setProperty("examplesStep", "\"{0}\"");
        patterns.setProperty("examplesStepsEnd", "]");
        patterns.setProperty("afterExamples", "}");
        patterns.setProperty("examplesTableStart", "\"parameters\": '{'");
        patterns.setProperty("examplesTableHeadStart", "\"names\": [");
        patterns.setProperty("examplesTableHeadCell", "\"{0}\"");
        patterns.setProperty("examplesTableHeadEnd", "]");
        patterns.setProperty("examplesTableBodyStart", "\"values\": [");
        patterns.setProperty("examplesTableRowStart", "[");
        patterns.setProperty("examplesTableCell", "\"{0}\"");
        patterns.setProperty("examplesTableRowEnd", "]");
        patterns.setProperty("examplesTableBodyEnd", "]");
        patterns.setProperty("examplesTableEnd", "}");
        patterns.setProperty("exampleScenariosStart", "\"examples\": [");
        patterns.setProperty("exampleScenariosEnd", "]");
        patterns.setProperty("example", "'{'\"keyword\": \"{0}\"");
        patterns.setProperty("parameterVerbatimStart", "[[");
        patterns.setProperty("parameterVerbatimEnd", "]]");
        patterns.setProperty("parameterValueStart", "((");
        patterns.setProperty("parameterValueEnd", "))");
        patterns.setProperty("parameterValueNewline", "\\n");
        patterns.setProperty("beforeComposedSteps", "\"steps\": [");
        patterns.setProperty("afterComposedSteps", "]");
        return patterns;
    }
}