/*******************************************************************************
* Copyright 2017 Ivan Shubin http://galenframework.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.galenframework.speclang2.pagespec;
import com.galenframework.parser.SyntaxException;
import com.galenframework.specs.page.PageSection;
import com.galenframework.specs.page.SpecGroup;
import com.galenframework.parser.StringCharReader;
import com.galenframework.speclang2.pagespec.rules.Rule;
import com.galenframework.parser.Expectations;
import com.galenframework.parser.StructNode;
import com.galenframework.specs.Spec;
import com.galenframework.specs.page.ObjectSpecs;
import com.galenframework.specs.Place;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import java.io.IOException;
import java.util.*;
import java.util.regex.Matcher;
public class PageSectionProcessor {
public static final String NO_OBJECT_NAME = null;
private final PageSpecHandler pageSpecHandler;
private final PageSection parentSection;
public PageSectionProcessor(PageSpecHandler pageSpecHandler) {
this.pageSpecHandler = pageSpecHandler;
this.parentSection = null;
}
public PageSectionProcessor(PageSpecHandler pageSpecHandler, PageSection parentSection) {
this.pageSpecHandler = pageSpecHandler;
this.parentSection = parentSection;
}
public void process(StructNode sectionNode) throws IOException {
if (sectionNode.getChildNodes() != null) {
String sectionName = sectionNode.getName().substring(1, sectionNode.getName().length() - 1).trim();
PageSection section = findSection(sectionName);
if (section == null) {
section = new PageSection(sectionName, sectionNode.getPlace());
if (parentSection != null) {
parentSection.addSubSection(section);
} else {
pageSpecHandler.addSection(section);
}
}
processSection(section, sectionNode.getChildNodes());
}
}
private void processSection(PageSection section, List<StructNode> childNodes) throws IOException {
for (StructNode sectionChildNode : childNodes) {
String childPlace = sectionChildNode.getName();
if (isSectionDefinition(childPlace)) {
new PageSectionProcessor(pageSpecHandler, section).process(sectionChildNode);
} else if (isRule(childPlace)) {
processSectionRule(section, sectionChildNode);
} else if (isObject(childPlace)) {
processObject(section, sectionChildNode);
} else {
throw new SyntaxException(sectionChildNode, "Unknown statement: " + childPlace);
}
}
}
private void processSectionRule(PageSection section, StructNode ruleNode) throws IOException {
String ruleText = ruleNode.getName().substring(1).trim();
Pair<PageRule, Map<String, String>> rule = findAndProcessRule(ruleText, ruleNode);
PageSection ruleSection = new PageSection(ruleText, ruleNode.getPlace());
section.addSubSection(ruleSection);
List<StructNode> resultingNodes;
try {
resultingNodes = rule.getKey().apply(pageSpecHandler, ruleText, NO_OBJECT_NAME, rule.getValue(), ruleNode.getChildNodes());
processSection(ruleSection, resultingNodes);
} catch (Exception ex) {
throw new SyntaxException(ruleNode, "Error processing rule: " + ruleText, ex);
}
}
private Pair<PageRule, Map<String, String>> findAndProcessRule(String ruleText, StructNode ruleNode) {
ListIterator<Pair<Rule, PageRule>> iterator = pageSpecHandler.getPageRules().listIterator(pageSpecHandler.getPageRules().size());
/*
It is important to make a reversed iteration over all rules so that
it is possible for the end user to override previously defined rules
*/
while (iterator.hasPrevious()) {
Pair<Rule, PageRule> rulePair = iterator.previous();
Matcher matcher = rulePair.getKey().getPattern().matcher(ruleText);
if (matcher.matches()) {
int index = 1;
Map<String, String> parameters = new HashMap<>();
for (String parameterName : rulePair.getKey().getParameters()) {
String value = matcher.group(index);
pageSpecHandler.setGlobalVariable(parameterName, value, ruleNode);
parameters.put(parameterName, value);
index += 1;
}
return new ImmutablePair<>(rulePair.getValue(), parameters);
}
}
throw new SyntaxException(ruleNode, "Couldn't find rule matching: " + ruleText);
}
private void processObjectLevelRule(ObjectSpecs objectSpecs, StructNode sourceNode) throws IOException {
String ruleText = sourceNode.getName().substring(1).trim();
Pair<PageRule, Map<String, String>> rule = findAndProcessRule(ruleText, sourceNode);
try {
pageSpecHandler.setGlobalVariable("objectName", objectSpecs.getObjectName(), sourceNode);
List<StructNode> specNodes = rule.getKey().apply(pageSpecHandler, ruleText, objectSpecs.getObjectName(), rule.getValue(), sourceNode.getChildNodes());
SpecGroup specGroup = new SpecGroup();
specGroup.setName(ruleText);
objectSpecs.addSpecGroup(specGroup);
for (StructNode specNode : specNodes) {
specGroup.addSpec(pageSpecHandler.getSpecReader().read(specNode.getName(), pageSpecHandler.getContextPath()));
}
} catch (Exception ex) {
throw new SyntaxException(sourceNode, "Error processing rule: " + ruleText, ex);
}
}
private boolean isRule(String nodeText) {
return nodeText.startsWith("|");
}
private PageSection findSection(String sectionName) {
if (parentSection != null) {
return findSection(sectionName, parentSection.getSections());
} else {
return findSection(sectionName, pageSpecHandler.getPageSections());
}
}
private PageSection findSection(String sectionName, List<PageSection> sections) {
for (PageSection section : sections) {
if (section.getName().equals(sectionName)) {
return section;
}
}
return null;
}
private void processObject(PageSection section, StructNode objectNode) throws IOException {
String name = objectNode.getName();
String objectExpression = name.substring(0, name.length() - 1).trim();
List<String> objectNames = pageSpecHandler.findAllObjectsMatchingStrictStatements(objectExpression);
for (String objectName : objectNames) {
if (objectNode.getChildNodes() != null && objectNode.getChildNodes().size() > 0) {
ObjectSpecs objectSpecs = findObjectSpecsInSection(section, objectName);
if (objectSpecs == null) {
objectSpecs = new ObjectSpecs(objectName);
section.addObjects(objectSpecs);
}
for (StructNode specNode : objectNode.getChildNodes()) {
if (isRule(specNode.getName())) {
processObjectLevelRule(objectSpecs, specNode);
} else {
processSpec(objectSpecs, specNode);
}
}
}
}
}
private void processSpec(ObjectSpecs objectSpecs, StructNode specNode) {
if (specNode.getChildNodes() != null && !specNode.getChildNodes().isEmpty()) {
throw new SyntaxException(specNode, "Specs cannot have inner blocks");
}
String specText = specNode.getName();
boolean onlyWarn = false;
if (specText.startsWith("%")) {
specText = specText.substring(1);
onlyWarn = true;
}
String alias = null;
StringCharReader reader = new StringCharReader(specText);
if (reader.firstNonWhiteSpaceSymbol() == '"') {
alias = Expectations.doubleQuotedText().read(reader);
specText = reader.getTheRest();
}
Spec spec;
try {
spec = pageSpecHandler.getSpecReader().read(specText, pageSpecHandler.getContextPath());
} catch (SyntaxException ex) {
ex.setPlace(specNode.getPlace());
throw ex;
}
spec.setOnlyWarn(onlyWarn);
spec.setAlias(alias);
if (specNode.getPlace() != null) {
spec.setPlace(new Place(specNode.getPlace().getFilePath(), specNode.getPlace().getLineNumber()));
}
spec.setProperties(pageSpecHandler.getProperties());
spec.setJsVariables(pageSpecHandler.getJsVariables());
objectSpecs.getSpecs().add(spec);
}
private ObjectSpecs findObjectSpecsInSection(PageSection section, String objectName) {
if (section.getObjects() != null) {
for (ObjectSpecs objectSpecs : section.getObjects()) {
if (objectSpecs.getObjectName().equals(objectName)) {
return objectSpecs;
}
}
}
return null;
}
private boolean isObject(String childPlace) {
return childPlace.endsWith(":");
}
public static boolean isSectionDefinition(String name) {
return name.startsWith("=") && name.endsWith("=");
}
}