package com.jazzautomation.cucumber.parser;
import com.jazzautomation.WebUIManager;
import com.jazzautomation.action.ComponentAction;
import com.jazzautomation.action.HtmlAction;
import com.jazzautomation.cucumber.And;
import com.jazzautomation.cucumber.Background;
import com.jazzautomation.cucumber.CucumberBase;
import static com.jazzautomation.cucumber.CucumberConstants.COLON;
import static com.jazzautomation.cucumber.CucumberConstants.ESCAPE_CHAR;
import static com.jazzautomation.cucumber.CucumberConstants.FEATURE;
import static com.jazzautomation.cucumber.CucumberConstants.LINE_END_MARK;
import static com.jazzautomation.cucumber.CucumberConstants.LINE_START_MARK;
import static com.jazzautomation.cucumber.CucumberConstants.OPTIONAL;
import static com.jazzautomation.cucumber.CucumberConstants.TABLE_COLUMN_SEPARATOR;
import static com.jazzautomation.cucumber.CucumberConstants.TABLE_COLUMN_SEPARATOR_CHAR;
import com.jazzautomation.cucumber.Feature;
import com.jazzautomation.cucumber.Scenario;
import com.jazzautomation.page.DomElement;
import static com.jazzautomation.util.Constants.SUPPORTED_BROWSERS;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
import static org.apache.commons.lang3.StringUtils.substringAfter;
import static org.apache.commons.lang3.StringUtils.substringAfterLast;
import static org.apache.commons.lang3.StringUtils.substringBeforeLast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* A singleton class to parse a Cucumber file at a time. The only public method is parse - take FileInputStream to parse through and return a Feature
* object. In case of ill-formats, throws IllegalCucumberFormatException.
*/
@SuppressWarnings({ "AssignmentToMethodParameter", "MethodMayBeStatic" })
public class FeatureParser
{
private static final String COMMENT_MARKER = "#";
private static Logger LOG = LoggerFactory.getLogger(FeatureParser.class);
private static FeatureParser instance;
/** Standard singleton implementation. */
public static FeatureParser getInstance()
{
if (instance == null)
{
instance = new FeatureParser();
}
return instance;
}
public static boolean isTaskOptional(String description)
{
boolean isOptional = false;
if (description.trim().toLowerCase().startsWith("("))
{ // it maybe an optional scenario
String possibleStringForOption = description.substring(description.indexOf('(') + 1);
LOG.info("Is this optional: " + possibleStringForOption);
if (possibleStringForOption.trim().toLowerCase().startsWith(OPTIONAL))
{
isOptional = true;
if (LOG.isDebugEnabled())
{
LOG.debug("Returning optional flag [" + isOptional + ']');
}
}
}
return isOptional;
}
public static Map<String, String> processMap(String text) throws IllegalCucumberFormatException
{
Map<String, String> keyValuePair = new LinkedHashMap<>();
List<String> lines = scanIntoLines(text);
// every line is a key value pair
for (String line : lines)
{
line = filterLineNumber(line);
if (isTableStructureValid(line))
{
String innerString = substringBeforeLast(line, TABLE_COLUMN_SEPARATOR);
String value = substringAfterLast(innerString, TABLE_COLUMN_SEPARATOR);
String key = substringBeforeLast(innerString, TABLE_COLUMN_SEPARATOR);
key = substringAfter(key, TABLE_COLUMN_SEPARATOR);
keyValuePair.put(key.trim(), value.trim());
}
else
{
if (isBlank(line))
{
continue;
}
if (line.trim().startsWith(COMMENT_MARKER))
{
LOG.info("Skipping line [" + line.trim() + ']');
continue;
}
throw new IllegalCucumberFormatException("The following line could not be processed; it was invalid. Line = [" + line + ']');
}
}
return keyValuePair;
}
public static List<String> scanIntoLines(String line)
{
List<String> list = new ArrayList<>();
list.addAll(Arrays.asList(line.split("\n")));
return list;
}
private static String filterLineNumber(String text)
{
if (text.startsWith(LINE_START_MARK))
{
return substringAfter(text, LINE_END_MARK);
}
else
{
return text;
}
}
// jsheridan CODEREVIEW - None of these have any documentation
private static boolean isTableStructureValid(String text)
{
// we should have three individual table separators
int count = 0;
for (int i = 0; i < text.length(); i++)
{
if (text.charAt(i) == TABLE_COLUMN_SEPARATOR_CHAR)
{
if (i == 0)
{
count++;
}
else if (text.charAt(i - 1) != ESCAPE_CHAR)
{
count++;
}
}
}
return count == 3;
}
public static void parseHoverAndClick(And and, String[] words, ComponentAction componentAction) throws IllegalCucumberFormatException
{
List<String> specialWords = retrieveSpecialWords(words);
if (specialWords.isEmpty())
{
int i = 0;
// find any thing after hover/click
for (; i < words.length; i++)
{
if (words[i].trim().toUpperCase().endsWith(HtmlAction.HOVER.toString())
|| words[i].trim().toUpperCase().endsWith(HtmlAction.CLICK.toString()))
{
break;
}
}
if ((i + 1) < words.length)
{
specialWords.add(words[i + 1]);
}
}
boolean hasWebComponent = false;
String componentName = "";
for (String word : specialWords)
{
if (canFindWebComponent(word))
{
hasWebComponent = true;
componentName = word;
break;
}
}
if (!hasWebComponent)
{
throw new IllegalCucumberFormatException("Illegal 'And' statement - cannot find a valid dom component in your And statement [" + and.getText()
+ "]. Please check your configuration.");
}
componentAction.setComponentName(componentName);
and.addActions(componentAction);
}
public static List<String> retrieveSpecialWords(String... words)
{
List<String> specialWords = new ArrayList<>();
for (String word : words)
{
if (word.trim().startsWith("\"") && word.trim().endsWith("\""))
{
specialWords.add(word.trim().substring(1, word.trim().length() - 1));
}
}
return specialWords;
}
public static boolean canFindWebComponent(String componentName)
{
DomElement webComponent = WebUIManager.getInstance().getDomElementFromPool(componentName.trim());
if (webComponent == null)
{ // if it is a special case: "-browser" option
for (String browser : SUPPORTED_BROWSERS)
{
webComponent = WebUIManager.getInstance().getDomElementFromPool(componentName.trim() + '-' + browser);
if (webComponent != null)
{
return true;
}
}
}
else
{
return true;
}
return false;
}
public static void parseWait(And and, String[] words, ComponentAction componentAction)
{
// find the "wait" word
int index = 0;
for (; index < words.length; index++)
{
if (words[index].trim().toUpperCase().equals(HtmlAction.WAIT.toString()))
{
break;
}
}
// can be 5s. 5 s, 5.0seconds, 5.0 seconds
if (index < (words.length - 1))
{
String waitValue = words[index + 1].trim().toLowerCase();
int sIndex = waitValue.indexOf('s');
if (sIndex != -1)
{
waitValue = waitValue.substring(0, sIndex);
}
componentAction.setActionValue(waitValue);
}
and.addActions(componentAction);
}
public static int setUpDescription(CucumberBase cucumber, List<String> lines, String leadingWord, String followingWord, String... endWords)
{
StringBuilder descBuffer = new StringBuilder();
descBuffer.append(filterWords(lines.get(0), leadingWord, followingWord)).append('\n');
int index = retrieveDescription(lines, descBuffer, endWords);
cucumber.setDescription(descBuffer.toString());
return index;
}
public static int retrieveDescription(List<String> lines, StringBuilder descBuffer, String... descriptionEndWords)
{
int index = 1;
while (index < lines.size())
{
String aline = filterLineNumber(lines.get(index));
if (isAtEnd(aline, descriptionEndWords))
{
break;
}
else
{
descBuffer.append(aline).append('\n');
index++;
}
}
return index;
}
public static String normalizeToString(List<String> lines, int index)
{
StringBuilder stringBuilder = new StringBuilder();
while (index < lines.size())
{
String line = lines.get(index);
stringBuilder.append(line).append('\n');
index++;
}
return stringBuilder.toString();
}
public static String filterWords(String text, String leadingWord, String followingWord)
{
String filteredString = filterLineNumber(text).trim();
if (filteredString.toLowerCase().startsWith(leadingWord))
{
filteredString = filteredString.substring(leadingWord.length()).trim();
if (filteredString.toLowerCase().startsWith(followingWord))
{
filteredString = filteredString.substring(followingWord.length()).trim();
}
}
return filteredString;
}
/** private constructor. */
private FeatureParser() {}
/**
* parse a file return a Feature object. Throw IllegalCucumberFormatException and stop if there is formatting errors
*
* @param originalLines
*/
public Feature parse(List<String> originalLines) throws IllegalCucumberFormatException
{
Feature feature = new Feature();
StringBuilder originalTextBuffer = new StringBuilder();
List<String> stringsForFile = processRawLines(originalLines, originalTextBuffer, 1);
feature.setOriginalText(originalTextBuffer.toString());
int index = setUpDescription(feature, stringsForFile);
index = setUpBackground(feature, stringsForFile, index);
setUpScenarios(feature, stringsForFile, index);
LOG.info("Successfully parsed feature: " + feature.getOriginalText());
return feature;
}
private List<String> processRawLines(List<String> originalLines, StringBuilder originalTextBuffer, int lineNum)
{
List<String> stringsForFile = new ArrayList<>();
for (String line : originalLines)
{
String trimmedLine = line.trim();
originalTextBuffer.append(line).append('\n');
String formattedLine = LINE_START_MARK + (lineNum++) + LINE_END_MARK + trimmedLine;
if (isNotEmpty(trimmedLine) && trimmedLine.startsWith(COMMENT_MARKER))
{ // skip adding comment lines to the executable feature.
stringsForFile.add("\n");
}
stringsForFile.add(formattedLine);
}
// add an extra line at the end of file.
stringsForFile.add("\n");
return stringsForFile;
}
private int setUpDescription(Feature feature, List<String> stringsForFile)
{
// setup feature description - background is optional
int index = setUpText(feature, stringsForFile, 0);
List<String> lines = scanIntoLines(feature.getText());
StringBuilder descBuffer = new StringBuilder();
descBuffer.append(filterWords(lines.get(0), FEATURE, COLON)).append('\n');
String[] descriptionEndWords = {};
retrieveDescription(lines, descBuffer, descriptionEndWords);
feature.setDescription(descBuffer.toString());
return index;
}
private int setUpBackground(Feature feature, List<String> stringsForFile, int index) throws IllegalCucumberFormatException
{
// setup background
Background background = new Background();
index = setUpText(background, stringsForFile, index);
if (background.getText() != null)
{
background.process();
feature.setBackground(background);
}
return index;
}
private void setUpScenarios(Feature feature, List<String> stringsForFile, int index) throws IllegalCucumberFormatException
{
// loop through scenarios and add to list
// setup scenarios
while (index < stringsForFile.size())
{
Scenario scenario = new Scenario();
index = setUpText(scenario, stringsForFile, index);
scenario.process();
feature.addScenario(scenario);
}
}
// jsheridan CODEREVIEW - unit test would be nice. What does this do?
public static int setUpText(CucumberBase cucumberObject, List<String> stringsForFile, int index)
{
String currentLine = stringsForFile.get(index);
String leadingWord = cucumberObject.getLeadingWord();
String[] endWords = cucumberObject.getEndWords();
if (isStartWithAWordAfterLineNumber(currentLine, leadingWord))
{
StringBuilder text = new StringBuilder();
text.append(currentLine).append('\n');
while (index < stringsForFile.size())
{
if (index == (stringsForFile.size() - 1))
{
break;
}
index++;
String nextLine = stringsForFile.get(index);
if (isAtEnd(nextLine, endWords))
{
break;
}
else
{ // take the line and move the index
text.append(nextLine).append('\n');
}
}
if (index == (stringsForFile.size() - 1))
{
index = stringsForFile.size();
}
cucumberObject.setText(text.toString());
}
return index;
}
public static boolean isStartWithAWordAfterLineNumber(String text, String word)
{
if (text.startsWith(LINE_START_MARK))
{
String filteredString = filterLineNumber(text);
return filteredString.trim().toLowerCase().startsWith(word.toLowerCase());
}
else
{
return text.trim().toLowerCase().startsWith(word.toLowerCase());
}
}
private static boolean isAtEnd(String text, String... endWords)
{
for (String word : endWords)
{
if (isStartWithAWordAfterLineNumber(text, word))
{
return true;
}
}
return false;
}
}