/* * (C) Copyright 2014 Java Test Automation Framework Contributors. * * 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 org.finra.jtaf.core.parsing; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.finra.jtaf.core.model.statement.Invocation; import org.finra.jtaf.core.model.statement.InvocationList; import org.finra.jtaf.core.parsing.exceptions.ExceptionAccumulator; import org.finra.jtaf.core.parsing.exceptions.ParsingException; import org.finra.jtaf.core.parsing.exceptions.UnexpectedElementException; import org.finra.jtaf.core.parsing.helpers.AttributeHelper; import org.finra.jtaf.core.parsing.helpers.ElementScanner; import org.finra.jtaf.core.parsing.helpers.ParserHelper; import org.finra.jtaf.core.utilities.logging.MessageCollector; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * Responsible for parsing statement in test script. */ public class StatementParser { private static final List<String> NON_BLOCK_PARAMETER_TYPES = Collections.unmodifiableList(Arrays.asList("param", "list", "string", "map", "boolean")); private static final List<String> STRING_PARAMETER_TYPES = Collections.unmodifiableList(Arrays.asList("param", "string")); public final InvocationList processStatementList(Element elem, MessageCollector mc) throws ParsingException { // Create formal try...recover...cleanup blocks preprocessStatementList(elem, mc); InvocationList retval = new InvocationList(); for (Element child : ParserHelper.getChildren(elem)) { retval.add(processStatement(child, mc)); } return retval; } //TODO: This needs to come across a TryRecoverCleanup and handle that private void preprocessStatementList(Element elem, MessageCollector mc) throws ParsingException { ElementScanner es = new ElementScanner(ParserHelper.getChildren(elem)); Element next = null; // Element tRC = // elem.getOwnerDocument().createElement("TryRecoverCleanup"); if (!elem.getNodeName().equals("TryRecoverCleanup")) { while (es.hasNext()) { if ((next = es.tryMatch("try")) != null) { Element safety = elem.getOwnerDocument().createElement("TryRecoverCleanup"); Element tryBlock = elem.getOwnerDocument().createElement("try"); NodeList childNodes = next.getChildNodes(); for (int i = 0; i < childNodes.getLength(); i++) { tryBlock.appendChild(childNodes.item(i).cloneNode(true)); } elem.replaceChild(safety, next); safety.appendChild(tryBlock); if ((next = es.tryMatch("recover")) != null) { elem.removeChild(next); Element recoverBlock = elem.getOwnerDocument().createElement("recover"); childNodes = next.getChildNodes(); for (int i = 0; i < childNodes.getLength(); i++) { recoverBlock.appendChild(childNodes.item(i).cloneNode(true)); } safety.appendChild(recoverBlock); } if ((next = es.tryMatch("cleanup")) != null) { elem.removeChild(next); Element cleanUpBlock = elem.getOwnerDocument().createElement("cleanup"); childNodes = next.getChildNodes(); for (int i = 0; i < childNodes.getLength(); i++) { cleanUpBlock.appendChild(childNodes.item(i).cloneNode(true)); } safety.appendChild(cleanUpBlock); } continue; } else if (((next = es.tryMatch("recover")) != null) || ((next = es.tryMatch("cleanup")) != null)) { throw new UnexpectedElementException(next); } // Move on to the next element es.match(); } } } private Invocation processStatement(Element elem, MessageCollector mc) throws ParsingException { try { mc.push(elem.getNodeName()); return processInvocation(elem, mc); } catch (ParsingException e) { mc.error(e.getMessage()); throw e; } finally { mc.pop(); } } // TODO Use this in order to put the child nodes of the command as the block // Calls processMap which calls process object. This method then deals with // strings, maps, and lists // This can be changed so that it either does processCommand or processX // depending no what // the default value is private Invocation processInvocation(Element elem, MessageCollector mc) throws ParsingException { Invocation retval = new Invocation(elem.getNodeName()); Map<String, List<Object>> invocationAttributesMap = new HashMap<String, List<Object>>(); // get properties from children // TODO: This will process all children of the command preprocessStatementList(elem, mc); invocationAttributesMap.putAll(processInvocationChildNodes(elem, mc)); // get properties from attributes final AttributeHelper attrs = new AttributeHelper(elem); // invocationAttributesMap.putAll(temp); retval.setParameters(invocationAttributesMap); retval.getParameters().putAll(attrs.getMap()); retval.getParameters().putAll(processMap(elem, mc)); return retval; } // TODO: This needs to send a child node to processInvocation if it is a // command private Map<String, List<Object>> processInvocationChildNodes(Element elem, MessageCollector mc) throws ParsingException { boolean blockCreated = false; try { mc.push(elem.getNodeName()); Map<String, List<Object>> result = new HashMap<String, List<Object>>(); List<Object> block = new ArrayList<Object>(); NodeList childNodes = elem.getChildNodes(); // there used to be a check for null here, but the JavaDoc makes no // mention that this is possible and I can find no examples online // of how to cause that if (childNodes.getLength() > 0) { for (int i = 0; i < childNodes.getLength(); i++) { Node currentNode = childNodes.item(i); if (currentNode.getNodeType() == Node.ELEMENT_NODE && !isNonBlockParameterType(currentNode.getNodeName())) { blockCreated = true; Invocation child = processInvocation((Element) currentNode, mc); block.add(child); /** * else { Object thing = processObject((Element) * currentNode, mc); block.add(thing); } **/ // TODO: processInvocation to get a command handled. // It should then be stored in result // processInvocation(currentNode, mc); // String nodeName = currentNode.getNodeName(); } } if (blockCreated) { result.put("blockParam", block); } } return result; } finally { mc.pop(); } } private boolean isNonBlockParameterType(String nodeName) { return NON_BLOCK_PARAMETER_TYPES.contains(nodeName.toLowerCase()); } private Map<String, Object> processMap(Element elem, MessageCollector mc) throws ParsingException { ExceptionAccumulator acc = new ExceptionAccumulator(); HashMap<String, Object> retval = new HashMap<String, Object>(); for (Element child : ParserHelper.getChildren(elem)) { try { final AttributeHelper attrs = new AttributeHelper(child); Object value = processObject(child, mc); if (value != null) { String key = attrs.getRequiredString("name"); retval.put(key, value); } } catch (Throwable th) { mc.error(th.getMessage()); acc.add(th); } } if (!acc.isEmpty()) { throw acc; } return retval; } private List<Object> processList(Element elem, MessageCollector mc) throws ParsingException { try { mc.push(elem.getNodeName()); ArrayList<Object> retval = new ArrayList<Object>(); for (Element child : ParserHelper.getChildren(elem)) { retval.add(processObject(child, mc)); } return retval; } finally { mc.pop(); } } private Object processObject(Element elem, MessageCollector mc) throws ParsingException { try { mc.push("In data element " + elem.getNodeName()); final String name = elem.getNodeName().toLowerCase(); // if (name.equals("param") || name.equals("string")) { if (isStringParameterType(name)) { return processString(elem, mc); } else if (name.equals("map")) { return processMap(elem, mc); } else if (name.equals("list")) { return processList(elem, mc); } else { return null; } } finally { mc.pop(); } } private boolean isStringParameterType(String parameterType) { return STRING_PARAMETER_TYPES.contains(parameterType.toLowerCase()); } // something strange is going on here. why can <string>'s have child nodes? why is this public? public Object processString(Element elem, MessageCollector mc) throws ParsingException { try { mc.push(elem.getNodeName()); Map<String, List<Object>> childNodeValue = processInvocationChildNodes(elem, mc); AttributeHelper ah = new AttributeHelper(elem); if (ah != null && ah.entrySet().size() > 0) { if (ah.containsKey("name") && (ah.entrySet().size() == 1)) { // this is map... } else { return ah; } } else if (!childNodeValue.isEmpty()) { for (String key : childNodeValue.keySet()) { return childNodeValue.get(key); } } return elem.getTextContent(); } finally { mc.pop(); } } }