/******************************************************************************* * 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.parser.StructNode; import com.galenframework.parser.StringCharReader; import java.io.IOException; import java.util.*; import static com.galenframework.parser.Expectations.doubleQuotedText; import static java.util.Arrays.asList; public class MacroProcessor { public static final String FOR_LOOP_KEYWORD = "@for"; public static final String FOR_EACH_LOOP_KEYWORD = "@forEach"; public static final String SET_KEYWORD = "@set"; public static final String OBJECTS_KEYWORD = "@objects"; public static final String GROUPS_KEYWORD = "@groups"; public static final String ON_KEYWORD = "@on"; public static final String IMPORT_KEYWORD = "@import"; public static final String SCRIPT_KEYWORD = "@script"; public static final String RULE_KEYWORD = "@rule"; public static final String RULE_BODY = "@ruleBody"; public static final String IF_KEYWORD = "@if"; public static final String ELSEIF_KEYWORD = "@elseif"; public static final String ELSE_KEYWORD = "@else"; public static final String DIE_KEYWORD = "@die"; private final PageSpecHandler pageSpecHandler; private List<String> macroOperators = asList( FOR_LOOP_KEYWORD, FOR_EACH_LOOP_KEYWORD, SET_KEYWORD, OBJECTS_KEYWORD, GROUPS_KEYWORD, ON_KEYWORD, IMPORT_KEYWORD, SCRIPT_KEYWORD, RULE_KEYWORD, RULE_BODY, DIE_KEYWORD ); private List<StructNode> currentRuleBody; public MacroProcessor(PageSpecHandler pageSpecHandler) { this.pageSpecHandler = pageSpecHandler; } public List<StructNode> process(List<StructNode> nodes) throws IOException { if (nodes != null) { List<StructNode> resultingNodes = new LinkedList<>(); ListIterator<StructNode> it = nodes.listIterator(); while (it.hasNext()) { StructNode node = it.next(); if (isConditionStatement(node.getName())) { resultingNodes.addAll(processConditionStatements(node, it)); } else { StructNode processedNode = pageSpecHandler.processExpressionsIn(node); if (isMacroStatement(processedNode.getName())) { resultingNodes.addAll(processMacroStatement(processedNode)); } else { resultingNodes.add(processNonMacroStatement(processedNode)); } } } return resultingNodes; } else { return Collections.emptyList(); } } private List<StructNode> processConditionStatements(StructNode ifNode, ListIterator<StructNode> it) throws IOException { List<StructNode> elseIfNodes = new LinkedList<>(); StructNode elseNode = null; boolean finishedConditions = false; while (it.hasNext() && !finishedConditions) { StructNode nextNode = it.next(); String firstWord = new StringCharReader(nextNode.getName()).readWord(); if (firstWord.equals(ELSEIF_KEYWORD)) { if (elseNode != null) { throw new SyntaxException(nextNode, "Cannot use elseif statement after else block"); } elseIfNodes.add(processExpressionsIn(nextNode)); } else if (firstWord.equals(ELSE_KEYWORD)) { if (elseNode != null) { throw new SyntaxException(nextNode, "Cannot use else statement after else block"); } elseNode = processExpressionsIn(nextNode); } else { finishedConditions = true; it.previous(); } } List<StructNode> nodesFromConditions = applyConditions(processExpressionsIn(ifNode), elseIfNodes, elseNode); return process(nodesFromConditions); } private StructNode processExpressionsIn(StructNode ifNode) { try { return pageSpecHandler.processExpressionsIn(ifNode); } catch (Exception ex) { throw new SyntaxException(ifNode, "JavaScript error inside statement"); } } private List<StructNode> applyConditions(StructNode ifNode, List<StructNode> elseIfNodes, StructNode elseNode) { if (isSuccessfullCondition(ifNode)) { return ifNode.getChildNodes(); } else if (elseIfNodes != null) { for (StructNode node : elseIfNodes) { if (isSuccessfullCondition(node)) { return node.getChildNodes(); } } } if (elseNode != null) { return elseNode.getChildNodes(); } return Collections.emptyList(); } private boolean isSuccessfullCondition(StructNode node) { StringCharReader reader = new StringCharReader(node.getName()); reader.readWord(); String booleanText = reader.readWord(); if (booleanText.isEmpty()) { throw new SyntaxException(node, "Missing boolean statement in condition"); } try { return Boolean.parseBoolean(booleanText); } catch (Exception ex) { throw new SyntaxException(node, "Couldn't parse boolean", ex); } } private boolean isConditionStatement(String name) { return IF_KEYWORD.equals(new StringCharReader(name).readWord()); } private StructNode processNonMacroStatement(StructNode processedNode) throws IOException { if (processedNode.getChildNodes() != null) { StructNode fullyProcessed = new StructNode(processedNode.getName()); fullyProcessed.setPlace(processedNode.getPlace()); fullyProcessed.setChildNodes(process(processedNode.getChildNodes())); return fullyProcessed; } else { return processedNode; } } private List<StructNode> processMacroStatement(final StructNode statementNode) throws IOException { StringCharReader reader = new StringCharReader(statementNode.getName()); String firstWord = reader.readWord(); if (FOR_LOOP_KEYWORD.equals(firstWord) || FOR_EACH_LOOP_KEYWORD.equals(firstWord)) { ForLoop forLoop = ForLoop.read(FOR_LOOP_KEYWORD.equals(firstWord), pageSpecHandler, reader, statementNode); return forLoop.apply(new LoopVisitor() { @Override public List<StructNode> visitLoop(Map<String, Object> variables) throws IOException { pageSpecHandler.setGlobalVariables(variables, statementNode); return process(statementNode.getChildNodes()); } }); } else if (SET_KEYWORD.equals(firstWord)) { return new SetVariableProcessor(pageSpecHandler).process(reader, statementNode); } else if (OBJECTS_KEYWORD.equals(firstWord)) { return new ObjectDefinitionProcessor(pageSpecHandler).process(reader, statementNode); } else if (GROUPS_KEYWORD.equals(firstWord)) { return new GroupsDefinitionProcessor(pageSpecHandler).process(reader, statementNode); } else if (ON_KEYWORD.equals(firstWord)) { return process(new OnFilterProcessor(pageSpecHandler).process(reader, statementNode)); } else if (IMPORT_KEYWORD.equals(firstWord)) { return new ImportProcessor(pageSpecHandler).process(reader, statementNode); } else if (SCRIPT_KEYWORD.equals(firstWord)) { return new ScriptProcessor(pageSpecHandler).process(reader, statementNode); } else if (RULE_KEYWORD.equals(firstWord)) { return new RuleProcessor(pageSpecHandler).process(reader, statementNode); } else if (ELSEIF_KEYWORD.equals(firstWord)) { throw new SyntaxException(statementNode, "elseif statement without if block"); } else if (ELSE_KEYWORD.equals(firstWord)) { throw new SyntaxException(statementNode, "else statement without if block"); } else if (DIE_KEYWORD.equals(firstWord)) { throw new SyntaxException(statementNode, doubleQuotedText().read(reader)); } else if (RULE_BODY.equals(firstWord)) { if (currentRuleBody != null) { return currentRuleBody; } else return Collections.emptyList(); } else { throw new SyntaxException(statementNode, "Invalid statement: " + firstWord); } } private boolean isMacroStatement(String name) { String firstWord = new StringCharReader(name).readWord(); return macroOperators.contains(firstWord); } public MacroProcessor setCurrentRuleBody(List<StructNode> currentRuleBody) { this.currentRuleBody = currentRuleBody; return this; } public List<StructNode> getCurrentRuleBody() { return currentRuleBody; } }