xquery-engine / src / main / expressions / ExpressionBuilder.java
ExpressionBuilder.java
Raw
package main.expressions;


import main.parsers.XGrammarBaseVisitor;
import main.parsers.XGrammarParser;
import org.antlr.v4.runtime.Parser;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.w3c.dom.*;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

public class ExpressionBuilder extends XGrammarBaseVisitor<NodeContainer>{
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */

    private NodeContainer nodeObj;
    @Override public NodeContainer visitProg(XGrammarParser.ProgContext ctx) {

        NodeContainer nodeContainer = visit(ctx.xq(0));
        nodeObj.validNodes = nodeContainer.validNodes;
        removeDuplicates(nodeObj);
       // nodeObj.printElements(); //to check results (remove print statement later)
        return nodeObj;
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitAp_Slash(XGrammarParser.Ap_SlashContext ctx) {
        visit(ctx.doc()); //populates with root document node
        //nodeObj.slashRule = true; //indicates to rp_tag rule to check children of current nodes
        visit(ctx.rp());
        //nodeObj.slashRule = false;
        return nodeObj;
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */


    @Override public NodeContainer visitAp_Dslash(XGrammarParser.Ap_DslashContext ctx) {
        visit(ctx.doc()); //populates with root document element
        //recursively/stack iteratively add all nodes for the second rp to check on (even text nodes)
        nodeObj.iterativeAddAll();
        return visit(ctx.rp());
        //System.out.println(nodeObj.rootDoc.getDocumentElement().getChildNodes().getLength());
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitRp_DSlash(XGrammarParser.Rp_DSlashContext ctx) {

        visit(ctx.rp(0));
        nodeObj.iterativeAddAll();
        return visit(ctx.rp(1));
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    //base method, get all children nodes
    @Override public NodeContainer visitRp_Wild(XGrammarParser.Rp_WildContext ctx) { //add all child nodes of the current visiting node
        //gets all children nodes
        ArrayList<Node> toReplace = new ArrayList<>(); //add all element nodes
        for (Node node : nodeObj.validNodes){
            NodeList tempList = node.getChildNodes();
            for (int i = 0; i < tempList.getLength(); i++){
                if (tempList.item(i).getNodeType() == Node.ELEMENT_NODE) { //adds only element node children
                    toReplace.add(tempList.item(i));
                }
            }
        }
        nodeObj.validNodes = toReplace;
        return nodeObj;
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitRp_Per(XGrammarParser.Rp_PerContext ctx) {
        //remain at current node or nodes
        return nodeObj;
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitRp_Filter(XGrammarParser.Rp_FilterContext ctx) {
        visit(ctx.rp());
        return visit(ctx.f());
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitRp_Concat(XGrammarParser.Rp_ConcatContext ctx) {
//      Check for duplicates after concatenation
        ArrayList<Node> og = new ArrayList<>(nodeObj.validNodes);

        NodeContainer rp1 = visit(ctx.rp(0)); //visit with current node object state
        ArrayList<Node> rp1List = rp1.validNodes;

        // Since nodes that were added to validNodes from the first operand shouldn't be considered for the second operand
        nodeObj.validNodes = og;
        NodeContainer rp2 = visit(ctx.rp(1)); //visit with current node object state
        ArrayList<Node> rp2List = rp2.validNodes;
        rp1List.addAll(rp2List);

        nodeObj.validNodes = rp1List;
        return removeDuplicates(nodeObj);
    }
//    @Override public NodeContainer visitRp_Concat(XGrammarParser.Rp_ConcatContext ctx) {
//
//        NodeContainer currentState = new NodeContainer(nodeObj);
//        //currentState.printElements();
//        NodeContainer rp1 = visit(ctx.rp(0)); //visit with current node object state
//        //rp1.printElements();
//        nodeObj = currentState;
//        NodeContainer rp2 = visit(ctx.rp(1)); //visit with current node object state
//        //rp2.printElements();
//
//        nodeObj.validNodes.clear();
//        nodeObj.validNodes.addAll(rp1.validNodes);
//        nodeObj.validNodes.addAll(rp2.validNodes);
//        return nodeObj;
//    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    //simple method, select tag name equivalent nodes
//    @Override public NodeContainer visitRp_Tag(XGrammarParser.Rp_TagContext ctx) {
//
//        ArrayList<Node> toReplace = new ArrayList<>();
//        System.out.println("Rp Tag " + ctx.getText());
//        //for all the children nodes check tag name against it
//        for (Node node : nodeObj.validNodes){
//            NodeList tempList = node.getChildNodes();
//
//            for (int i = 0; i < tempList.getLength(); i++){
//                if ((tempList.item(i).getNodeType() == Node.ELEMENT_NODE) && tempList.item(i).getNodeName().equals(ctx.TEXT().getText())){
////                    System.out.println("Adding: " + tempList.item(i).getNodeName());
//                    toReplace.add(tempList.item(i));
//                }
//            }
//        }
//        nodeObj.validNodes = toReplace;
//        System.out.println("Tag: " + toReplace);
//        System.out.println(ctx.getText());
//        return nodeObj;
//        // if (nodeObj.validNodes.size() > 0){
//        //     nodeObj.validNodes.removeIf(n -> (n.getNodeType() == Node.ELEMENT_NODE && !(n.getNodeName().equals(ctx.TEXT().getText()))));
//        // }
//    }
    @Override public NodeContainer visitRp_Tag(XGrammarParser.Rp_TagContext ctx) {

        ArrayList<Node> toReplace = new ArrayList<>();
        nodeObj.addOnlyChildren();
        //if (nodeObj.slashRule == true){ //indicates the most recent / or // ancestor was a / type, add all children and check tag name against that
        //    nodeObj.addOnlyChildren();
        //}
        //for all current nodes, check tag name against it (must be an element node)
        for (Node node : nodeObj.validNodes){
            if ((node.getNodeType() == Node.ELEMENT_NODE) && node.getNodeName().equals(ctx.TAGNAME().getText())) {
                toReplace.add(node);
            }
        }
        nodeObj.validNodes = toReplace;
//        System.out.println("In rp tag: " + nodeObj.validNodes);
        return nodeObj;
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    //base method, get text of current node
    //base method, get all the text node descendants of current nodes
    @Override public NodeContainer visitRp_Text(XGrammarParser.Rp_TextContext ctx) {
        ArrayList<Node> textNodes = new ArrayList<>();
        NodeList nl;
        for (Node node: nodeObj.validNodes) {
            nl = node.getChildNodes();
            for (int i = 0; i < nl.getLength(); i++) {
                if (nl.item(i).getNodeType() == Node.TEXT_NODE) {
                    textNodes.add(nl.item(i));
                }
            }
        }
        nodeObj.validNodes = textNodes;
        return nodeObj;
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    //base method, get parent node of current visiting node
    @Override public NodeContainer visitRp_Dper(XGrammarParser.Rp_DperContext ctx) {
        /*
            since its possible to have more than one parent nodes in the validNodes list, but
            many of the nodes will have the same parent node, -> remove duplicates at end
            ensure that there is a valid parent node, otherwise indicate already at root;
        */
        Set<Node> parentSet = new LinkedHashSet<>();
        for (Node node : nodeObj.validNodes){
            if (node == nodeObj.rootDoc){
                //System.out.println("Already at the root node, unable to generate result\n");
                return nodeObj;
            }
            parentSet.add(node.getParentNode());
        }
        //nodeObj.validNodes.clear();
        //nodeObj.validNodes.addAll(parentSet);
        nodeObj.validNodes = new ArrayList<>(parentSet);
        return nodeObj;
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */

    @Override public NodeContainer visitRp_Slash(XGrammarParser.Rp_SlashContext ctx) {
        visit(ctx.rp(0));
        //nodeObj.slashRule = true;
        visit(ctx.rp(1));
        //nodeObj.slashRule = false;
        return nodeObj;
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    //base method, call DOM attribute method
    //fix this and test
    @Override public NodeContainer visitRp_Attr(XGrammarParser.Rp_AttrContext ctx) {
        ArrayList<Node> attrNodes = new ArrayList<>();
        String attr;
        for (int i = 0; i < nodeObj.validNodes.size(); i++) {
            if (nodeObj.validNodes.get(i).hasAttributes()) {
                if (ctx.children.size() == 2) {
                    attr = ctx.getChild(1).getText();
                } else{
                    continue;
                }
                NamedNodeMap attrMap = nodeObj.validNodes.get(i).getAttributes();
                if (attrMap.getNamedItem(attr) != null) {
                    attrNodes.add(attrMap.getNamedItem(attr));
                }
            }
        }
        nodeObj.validNodes = attrNodes;
        return nodeObj;
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    //(rp) = rp
    @Override public NodeContainer visitRp_Par(XGrammarParser.Rp_ParContext ctx) {
        return visit(ctx.rp()); //skip parenthesis tokens
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitF_Par(XGrammarParser.F_ParContext ctx) {
        return visit(ctx.f());
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitF_Not(XGrammarParser.F_NotContext ctx) {
        ArrayList<Node> og = new ArrayList<>(nodeObj.validNodes);
        ArrayList<Node> nodes = visit(ctx.f()).validNodes;
        og.removeAll(nodes);
        nodeObj.validNodes = new ArrayList<>(og);
        return nodeObj;
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitF_Or(XGrammarParser.F_OrContext ctx) {
        ArrayList<Node> og = new ArrayList<>(nodeObj.validNodes);
        ArrayList<Node> evalFilter = visit(ctx.f(0)).validNodes;
        nodeObj.validNodes = og;
        evalFilter.addAll(visit(ctx.f(1)).validNodes);
        evalFilter = removeDuplicates(evalFilter);
        nodeObj.validNodes = evalFilter;

        return nodeObj;
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitF_Const(XGrammarParser.F_ConstContext ctx) {
        if (ctx.EQ().getText().equalsIgnoreCase("eq")) {
            throw new RuntimeException("String cannot be compared using the 'eq' operator." +
                    "Use '=' instead");
        }
        ArrayList<Node> og = new ArrayList<>(nodeObj.validNodes);
        TerminalNode stringConstant = ctx.STRINGCONST();
        ArrayList<Node> returnList = new ArrayList<>();

        for (Node ogNode : og) {
            nodeObj.validNodes = new ArrayList<>();
            nodeObj.validNodes.add(ogNode);
            ArrayList<Node> nodes = visit(ctx.rp()).validNodes;
            for (Node node : nodes) {
                if (node.getTextContent().equals(stringConstant.getText().substring(1, stringConstant.getText().length()-1))) {
                    returnList.add(ogNode);
                }

                //System.out.println("Node Type: " + node.getNodeType());
//                System.out.println("Node Content: " + node.getTextContent());
                // Potential issue --> does not differentiate between different kinds of whitespaces
                //ArrayList<String> parts = new ArrayList<>(
                //        Arrays.asList(node.getTextContent().split(" \t\r\n")));
                //String sc = stringConstant.stream().map(ParseTree::getText)
                //        .collect(Collectors.joining(" "));
//                System.out.println(Node.TEXT_NODE + " " + sc.equals(String.join(" ", parts)));
//                if (node.getNodeType() == Node.TEXT_NODE
//                        && String.join(" ", parts).equals(sc)) {
                //if (String.join(" ", parts).equals(sc)) {
                    // Even if there's one match, move on to the next obj
               //     returnList.add(ogNode);
               //     break;
            }
        }
        nodeObj.validNodes = returnList;
        return nodeObj;
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitF_And(XGrammarParser.F_AndContext ctx) {
        ArrayList<Node> og = new ArrayList<>(nodeObj.validNodes);
        ArrayList<Node> nodes0 = visit(ctx.f(0)).validNodes;
        nodeObj.validNodes = og;
        ArrayList<Node> nodes1 = visit(ctx.f(1)).validNodes;
        ArrayList<Node> result = new ArrayList<>();
        for (Node node : nodes0) {
            if (nodes1.contains(node)) {
                result.add(node);
            }
        }
        nodeObj.validNodes = result;

        return nodeObj;
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitF_Rp(XGrammarParser.F_RpContext ctx) {
        ArrayList<Node> og = new ArrayList<>(nodeObj.validNodes);
        ArrayList<Node> wl = visit(ctx.rp()).validNodes;
        if (!wl.isEmpty()) {
            nodeObj.validNodes = og;
        } else {
            nodeObj.validNodes = new ArrayList<>();
        }
        return nodeObj;
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitF_Eq(XGrammarParser.F_EqContext ctx) {
        // Need to test this method --> test cases don't have an example of this query
        ArrayList<Node> og = new ArrayList<>(nodeObj.validNodes);
        ArrayList<Node> returnList = new ArrayList<>();

        for (Node ogNode : og) {
            nodeObj.validNodes = new ArrayList<>();
            nodeObj.validNodes.add(ogNode);
            ArrayList<Node> op1 = visit(ctx.rp(0)).validNodes;
            nodeObj.validNodes = new ArrayList<>();
            nodeObj.validNodes.add(ogNode);
            ArrayList<Node> op2 = visit(ctx.rp(1)).validNodes;

            for (Node node1 : op1) {
                for (Node node2: op2) {
                    if (node1.isEqualNode(node2)) {
                        returnList.add(ogNode);
                        break;
                    }
                }
            }
        }
        nodeObj.validNodes = returnList;
        return nodeObj;

    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitF_Is(XGrammarParser.F_IsContext ctx) {
        ArrayList<Node> og = new ArrayList<>(nodeObj.validNodes);
        ArrayList<Node> returnList = new ArrayList<>();

        for (Node ogNode : og) {
            nodeObj.validNodes = new ArrayList<>();
            nodeObj.validNodes.add(ogNode);
            ArrayList<Node> op1 = visit(ctx.rp(0)).validNodes;
            nodeObj.validNodes = new ArrayList<>();
            nodeObj.validNodes.add(ogNode);
            ArrayList<Node> op2 = visit(ctx.rp(1)).validNodes;

            for (Node node1 : op1) {
                for (Node node2: op2) {
                    if (node1.isSameNode(node2)) {
                        returnList.add(ogNode);
                        break;
                    }
                }
            }
        }
        nodeObj.validNodes = returnList;
        return nodeObj;
    }

    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitDoc(XGrammarParser.DocContext ctx) {
        //System.out.println("Visited filename and assuming its valid " + ctx.getText());
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();

            String filename = " ";
            if (ctx.getText().contains("doc(")) {
                //System.out.println(ctx.getText().substring(5, (ctx.getText().length() - 2)));
                filename = ctx.getText().substring(5, (ctx.getText().length() - 2));
            }
            if (ctx.getText().contains("document(")){
                //System.out.println(ctx.getText().substring(10, (ctx.getText().length()-2)));
                filename = ctx.getText().substring(10, (ctx.getText().length() - 2));
            }

            //System.out.println(ctx.getText().split("./" + "[\"']")[1]);
            //System.out.println("Reading file: " + filename);
            Document doc = db.parse(new File("./", filename));
            doc.getDocumentElement().normalize();
            if (nodeObj == null){ //nested flwr statement (don't replace var bindings)
                nodeObj = new NodeContainer(doc);
            } else{
                //keep the current var bindings, but clear out the
                //nodeObj.validNodes.clear();
                //nodeObj.validNodes.add(doc);
                nodeObj.validNodes = new ArrayList<>(Arrays.asList(doc));
            }
            //System.out.println("Root Element " + doc.getFirstChild().getNodeName());
            //NodeList nl = doc.getElementsByTagName("ACT");
            return nodeObj;
        }
        catch (ParserConfigurationException | SAXException | IOException e) {
            e.printStackTrace();
        }
        return new NodeContainer();
    }

    public NodeContainer removeDuplicates(NodeContainer nodeContainer) {
        Set<Node> set = new LinkedHashSet<>(nodeContainer.validNodes);
        nodeContainer.validNodes = new ArrayList<>(set);
        //nodeContainer.validNodes.clear();
        //nodeContainer.validNodes.addAll(set);
        return nodeContainer;
    }

    public ArrayList<Node> removeDuplicates(ArrayList<Node> nodes) {
        Set<Node> set = new LinkedHashSet<>(nodes);
        return new ArrayList<>(set);
    }


    //XQuery Evaluation Implementations
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitXq_Const(XGrammarParser.Xq_ConstContext ctx) { //edit

        //creates a new text node and node container object with string const as text node data and returns

        String varName = ctx.STRINGCONST().getText();
        varName = varName.substring(1, varName.length()-1); //ignore quotes

        NodeContainer toReturn = new NodeContainer();
        if (nodeObj == null) {
            DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder docBuilder = null;
            try {
                docBuilder = docFactory.newDocumentBuilder();
            } catch (ParserConfigurationException e) {
                throw new RuntimeException(e);
            }
            Document doc = docBuilder.newDocument();
            Node textNode = doc.createTextNode(varName);
            toReturn.addElement(textNode);
        } else{
            nodeObj.rootDoc.setXmlStandalone(true);
            Node textNode = nodeObj.rootDoc.createTextNode(varName);
            toReturn.addElement(textNode);
        }
        return toReturn;
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitXp_Par(XGrammarParser.Xp_ParContext ctx) {
        return visit(ctx.xq());
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitXq_Dslash(XGrammarParser.Xq_DslashContext ctx) {
        //get any constructed nodes or return nodes (singular node values not added to nodeObj already) - examples of this are all Xqueries minus return statements where nodeObj is actually updated
        nodeObj.validNodes = visit(ctx.xq()).validNodes;
        nodeObj.iterativeAddAll();
        visit(ctx.rp()); //performs functionality on nodeObj itself
        return removeDuplicates(nodeObj);
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitXq_Concat(XGrammarParser.Xq_ConcatContext ctx) {
        ArrayList<Node> og = new ArrayList<>();
        if (nodeObj != null) {
            og = nodeObj.validNodes;
        }

        NodeContainer rp1 = visit(ctx.xq(0)); //visit with current node object state
        ArrayList<Node> rp1List = new ArrayList<Node>(rp1.validNodes);

        // Since nodes that were added to validNodes from the first operand shouldn't be considered for the second operand
        nodeObj.validNodes = og;
        NodeContainer rp2 = visit(ctx.xq(1)); //visit with current node object state
        ArrayList<Node> rp2List = new ArrayList<Node>(rp2.validNodes);
        rp1List.addAll(rp2List);

        nodeObj.validNodes = rp1List;
        return removeDuplicates(nodeObj);
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitXq_Ap(XGrammarParser.Xq_ApContext ctx) {
        visit(ctx.ap());
        return removeDuplicates(nodeObj);
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitXq_Slash(XGrammarParser.Xq_SlashContext ctx) {
        nodeObj.validNodes = visit(ctx.xq()).validNodes;
        visit(ctx.rp());
        return removeDuplicates(nodeObj);
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitXq_Var(XGrammarParser.Xq_VarContext ctx) {
        NodeContainer nodeContainer = new NodeContainer();
        ArrayList<Node> getList = new ArrayList<>();
        String varName = ctx.VAR().getText();
        if (nodeObj.varBindings.containsKey(varName)) {
           getList = nodeObj.varBindings.get(varName);
        } else{
            System.out.println(varName);
            throw new RuntimeException("Invalid Var Name");
        }
        nodeContainer.validNodes = getList;
        return nodeContainer;
    }

    //DFS recursive helper
    public ArrayList<Node> forHelper(XGrammarParser.Xq_FLWRContext ctx, ArrayList<Node> combinedResults, int nestIndex, int nestDepth) {
        //base case
        if (nestIndex >= nestDepth){ //if the index of the current nested index is out of bounds, perform current return on current indices
            if (ctx.letClause() != null){
                visit(ctx.letClause()); //only updates nodeObj varBindings
            }
            if (ctx.whereClause() != null){
                NodeContainer whereContainer = visit(ctx.whereClause()); //filters based on where condition
                if (whereContainer.validNodes.size() == 0) { //skip return
                    return combinedResults;
                }
            }
            combinedResults.addAll(visit(ctx.returnClause()).validNodes);
            return combinedResults;
        }
        String varName = ctx.forClause().VAR(nestIndex).getText(); //current variable to be moved and updated
        ArrayList<Node> toTraverse = new ArrayList<>(visit(ctx.forClause().xq(nestIndex)).validNodes); //evaluating xq at the position and copy the list
        for (Node aNode: toTraverse) {
            ArrayList<Node> arrayList = new ArrayList<>();
            arrayList.add(aNode);
            nodeObj.varBindings.put(varName, arrayList); //will replace any prior set value
            combinedResults = forHelper(ctx, combinedResults, nestIndex + 1, nestDepth);
        }
        return combinedResults;
    }

    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitXq_FLWR(XGrammarParser.Xq_FLWRContext ctx) {

        /*
            Generate a node list which: alters the binding at each nested loop iterator (for var_1 in Xq_1, for var_2 in XQ_2 ....)
            then let assigns more variables (this may be entire lists)
            evaluate where clause
            then evaluate the return statement where you are familiar with the previous n+k variables
         */
        //NodeContainer iteratorLists = visit(ctx.forClause()); //get all the iterator lists
  //      NodeContainer nodeContainer = new NodeContainer();
  //
        HashMap<String, ArrayList<Node>> context0 = new HashMap<>();
        if (nodeObj != null) {
            context0 = nodeObj.varBindings; //save current scope
        }
        ArrayList<Node> toAdd = new ArrayList<>();
        ArrayList<Node> toReturn = new ArrayList<>();
        int nestDepth = ctx.forClause().VAR().size(); //total number of nested for loops
        int nestIndex = 0;
        toReturn = forHelper(ctx, toAdd, nestIndex, nestDepth);
        nodeObj.validNodes = toReturn;
        nodeObj.varBindings = context0;
        //NodeContainer nodeContainer = new NodeContainer();
        //nodeContainer.validNodes = toReturn;
        return nodeObj;
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */

    //will every possible case of a constructed node at some point be a final node? - Double check this
    @Override public NodeContainer visitXq_Tag(XGrammarParser.Xq_TagContext ctx) {
        NodeContainer nodeContainer = visit(ctx.xq()); //first visit xq then build
        // nodeObj.validNodes.addAll(nodeContainer.validNodes);
        nodeObj.validNodes = removeDuplicates(nodeContainer).validNodes;
        // nodeObj.validNodes = nodeContainer.validNodes; //TODO: CHECK
        String tagName = ctx.TAGNAME(0).getText();

        Node elementNode;

        if (nodeObj != null) {
            nodeObj.rootDoc.setXmlStandalone(true);
            elementNode = nodeObj.rootDoc.createElement(tagName);
        } else{
            //elementNode = nodeObj.rootDoc.createElement(tagName);
            DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder docBuilder = null;
            try {
                docBuilder = docFactory.newDocumentBuilder();
            } catch (ParserConfigurationException e) {
                throw new RuntimeException(e);
            }
            Document doc = docBuilder.newDocument();
            elementNode = doc.createTextNode(tagName);
        }

        for (Node node: nodeObj.validNodes){
            if (node != null) {
                elementNode.appendChild(nodeObj.rootDoc.importNode(node, true)); //append deep copy of node
            }
        }
        NodeContainer toReturn = new NodeContainer();
        toReturn.addElement(elementNode);
        return toReturn;
    }

    @Override
    public NodeContainer visitXq_Tag2(XGrammarParser.Xq_Tag2Context ctx) {
        NodeContainer nodeContainer = visit(ctx.xq()); //first visit xq then build
        // nodeObj.validNodes.addAll(nodeContainer.validNodes);
        nodeObj.validNodes = removeDuplicates(nodeContainer).validNodes;
        // nodeObj.validNodes = nodeContainer.validNodes; //TODO: CHECK
        String tagName = ctx.TAGNAME(0).getText();

        Node elementNode;

        if (nodeObj != null) {
            nodeObj.rootDoc.setXmlStandalone(true);
            elementNode = nodeObj.rootDoc.createElement(tagName);
        } else{
            //elementNode = nodeObj.rootDoc.createElement(tagName);
            DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder docBuilder = null;
            try {
                docBuilder = docFactory.newDocumentBuilder();
            } catch (ParserConfigurationException e) {
                throw new RuntimeException(e);
            }
            Document doc = docBuilder.newDocument();
            elementNode = doc.createTextNode(tagName);
        }

        for (Node node: nodeObj.validNodes){
            if (node != null) {
                elementNode.appendChild(nodeObj.rootDoc.importNode(node, true)); //append deep copy of node
            }
        }
        NodeContainer toReturn = new NodeContainer();
        toReturn.addElement(elementNode);
        return toReturn;
    }

    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitXq_Let(XGrammarParser.Xq_LetContext ctx) {
        HashMap<String, ArrayList<Node>> ogVarBindings = new HashMap<>();
        if (nodeObj != null) {
             ogVarBindings = nodeObj.varBindings;
        }
        NodeContainer letContainer = visit(ctx.letClause());
        nodeObj.validNodes = letContainer.validNodes;
        NodeContainer returned = visit(ctx.xq());
        nodeObj.varBindings = ogVarBindings;
        return returned;
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitForClause(XGrammarParser.ForClauseContext ctx) {
        /*
        NodeContainer iteratorLists = new NodeContainer();
        int varCount = ctx.VAR().size(); //collects lists of all the iterators
        for (int i = 0; i < varCount; i++){
            NodeContainer visited = visit(ctx.xq(i));
            iteratorLists.varBindings.put(ctx.VAR(i).getText(), visited.validNodes); //overwrite any previous bindings
        }
        return iteratorLists;

         */
        return visitChildren(ctx);
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitLetClause(XGrammarParser.LetClauseContext ctx) {
        //start with original context
        //then for each variable bind the result of the xquery and add to the prior context
        int varCount = ctx.VAR().size();
        for (int i = 0; i < varCount; i++){
            NodeContainer visited = visit(ctx.xq(i));
            nodeObj.varBindings.put(ctx.VAR(i).getText(), visited.validNodes); //any same key name will be overwritten
           // if (nodeObj.varBindings.containsKey(ctx.VAR(i).getText()))
           //     throw new RuntimeException("Duplicate variable names found, please use unique variable names");
           // else
        }
        return nodeObj;
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitWhereClause(XGrammarParser.WhereClauseContext ctx) {
        return visit(ctx.cond());
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitReturnClause(XGrammarParser.ReturnClauseContext ctx) {
//        NodeContainer returned = new NodeContainer(visit(ctx.xq()));
//        return returned;
        return visit(ctx.xq());
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitCond_And(XGrammarParser.Cond_AndContext ctx) {
        ArrayList<Node> og = new ArrayList<>();
        if (nodeObj != null) {
            og = nodeObj.validNodes;
        }
        //ArrayList<Node> og = new ArrayList<>(nodeObj.validNodes);
        ArrayList<Node> nodes0 = visit(ctx.cond(0)).validNodes;
        nodeObj.validNodes = og;
        ArrayList<Node> nodes1 = visit(ctx.cond(1)).validNodes;
        ArrayList<Node> result = new ArrayList<>();
        for (Node node : nodes0) {
            if (nodes1.contains(node)) {
                result.add(node);
            }
        }
        //nodeObj.validNodes = result;
        NodeContainer toReturn = new NodeContainer();
        toReturn.validNodes = result;
        return toReturn;
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitCond_Eq(XGrammarParser.Cond_EqContext ctx) {

        ArrayList<Node> og = new ArrayList<>();
        if (nodeObj != null) {
            og = nodeObj.validNodes;
        }
        //ArrayList<Node> og = new ArrayList<>(nodeObj.validNodes);
        ArrayList<Node> returnList = new ArrayList<>();

        for (Node ogNode : og) {
            nodeObj.validNodes = new ArrayList<>();
            nodeObj.validNodes.add(ogNode);
            ArrayList<Node> op1 = visit(ctx.xq(0)).validNodes;
            nodeObj.validNodes = new ArrayList<>();
            nodeObj.validNodes.add(ogNode);
            ArrayList<Node> op2 = visit(ctx.xq(1)).validNodes;
            for (Node node1 : op1) {
                for (Node node2 : op2) {
                    if (node1.isEqualNode(node2)) {
                        returnList.add(ogNode);
                    }
                    // The following branch if uncommented will support
                    // equalities of the form node = "string"
                    else if (node1.getTextContent().equals(node2.getTextContent())) {
                        returnList.add(ogNode);
                    }
                }
            }
        }
        NodeContainer toReturn = new NodeContainer();
        toReturn.validNodes = returnList;
        return toReturn;
    }

    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitCond_Is(XGrammarParser.Cond_IsContext ctx) {
        ArrayList<Node> og = new ArrayList<>();
        if (nodeObj != null) {
            og = nodeObj.validNodes;
        }
       // ArrayList<Node> og = new ArrayList<>(nodeObj.validNodes);
        ArrayList<Node> returnList = new ArrayList<>();

        for (Node ogNode : og) {
            nodeObj.validNodes = new ArrayList<>();
            nodeObj.validNodes.add(ogNode);
            ArrayList<Node> op1 = visit(ctx.xq(0)).validNodes;
            nodeObj.validNodes = new ArrayList<>();
            nodeObj.validNodes.add(ogNode);
            ArrayList<Node> op2 = visit(ctx.xq(1)).validNodes;

            for (Node node1 : op1) {
                for (Node node2: op2) {
                    if (node1.isSameNode(node2)) {
                        returnList.add(ogNode);
                        break;
                    }
                }
            }
        }
        //nodeObj.validNodes = returnList;
        NodeContainer toReturn = new NodeContainer();
        toReturn.validNodes = returnList;
        return toReturn;
    }

    public ArrayList<Node> someHelper(XGrammarParser.Cond_SomeContext ctx, ArrayList<Node> combinedResults, int nestIndex, int nestDepth) {
        if (nestIndex >= nestDepth) {
            // Recurrence termination condition
            NodeContainer conditionResult = visit(ctx.cond());
            if (conditionResult.validNodes.size() > 0) {
                combinedResults.addAll(conditionResult.validNodes);
            }
            return combinedResults;
        }

        String varName = ctx.VAR(nestIndex).getText(); //current variable to be moved and updated
        ArrayList<Node> toTraverse = new ArrayList<>(visit(ctx.xq(nestIndex)).validNodes); //evaluating xq at the position and copy the list

        for (Node aNode: toTraverse) {
            ArrayList<Node> arrayList = new ArrayList<>();
            arrayList.add(aNode);
            nodeObj.varBindings.put(varName, arrayList); //will replace any prior set value
            combinedResults = someHelper(ctx, combinedResults, nestIndex + 1, nestDepth);
        }
        return combinedResults;
    }


    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitCond_Some(XGrammarParser.Cond_SomeContext ctx) {

        HashMap<String, ArrayList<Node>> ogVarBindings = new HashMap<>();
        if (nodeObj != null) {
            ogVarBindings = nodeObj.varBindings;
        }

        ArrayList<Node> toAdd = new ArrayList<>();
        ArrayList<Node> toReturn = new ArrayList<>();
        int nestDepth = ctx.VAR().size(); //total number of nested for loops
        int nestIndex = 0;

        toReturn = someHelper(ctx, toAdd, nestIndex, nestDepth);
        NodeContainer returnContainer = new NodeContainer();
        returnContainer.validNodes = toReturn;
        nodeObj.varBindings = ogVarBindings;
        return returnContainer;
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitCond_Not(XGrammarParser.Cond_NotContext ctx) {
        ArrayList<Node> og = new ArrayList<>();
        if (nodeObj != null) {
            og = nodeObj.validNodes;
        }
        //ArrayList<Node> og = new ArrayList<>(nodeObj.validNodes);
        ArrayList<Node> nodes = visit(ctx.cond()).validNodes;
        og.removeAll(nodes);
       // nodeObj.validNodes = new ArrayList<>(og);
        NodeContainer toReturn = new NodeContainer();
        toReturn.validNodes = og;
        return toReturn;
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitCond_Par(XGrammarParser.Cond_ParContext ctx) {
        return visit(ctx.cond());
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitCond_Empty(XGrammarParser.Cond_EmptyContext ctx) {
        // ToDo: Untested method --> test logic too
        ArrayList<Node> og = new ArrayList<>();
        if (nodeObj != null) {
            og = nodeObj.validNodes;
        }
        //ArrayList<Node> og = new ArrayList<>(nodeObj.validNodes);
        ArrayList<Node> nodes = visit(ctx.xq()).validNodes;
        ArrayList<Node> resultNodes = new ArrayList<>();

        for (Node node: og) {
            nodeObj.validNodes = new ArrayList<>();
            nodeObj.validNodes.add(node);
            if (visit(ctx.xq()).validNodes.isEmpty())
                resultNodes.add(node);
        }
        NodeContainer toReturn = new NodeContainer();
        toReturn.validNodes = resultNodes;
        return toReturn;
    }
    /**
     * {@inheritDoc}
     *
     * <p>The default implementation returns the result of calling
     * {@link #visitChildren} on {@code ctx}.</p>
     */
    @Override public NodeContainer visitCond_Or(XGrammarParser.Cond_OrContext ctx) {
        ArrayList<Node> og = new ArrayList<>();
        if (nodeObj != null) {
            og = nodeObj.validNodes;
        }
        ArrayList<Node> evalFilter = visit(ctx.cond(0)).validNodes;
        nodeObj.validNodes = og;
        evalFilter.addAll(visit(ctx.cond(1)).validNodes);
        evalFilter = removeDuplicates(evalFilter);
        NodeContainer toReturn = new NodeContainer();
        toReturn.validNodes = evalFilter;
        return toReturn;
    }


    @Override
    public NodeContainer visitXq_Join(XGrammarParser.Xq_JoinContext ctx) {
        NodeContainer returnContainer = new NodeContainer();
        NodeContainer visited = visit(ctx.join());
        ArrayList<Node> ogValidNodes = new ArrayList<>(nodeObj.validNodes);

        for (Node node: visited.validNodes) {
            nodeObj.validNodes = new ArrayList<>();
            nodeObj.validNodes.add(node);
            nodeObj.varBindings.put(ctx.VAR().getText(), nodeObj.validNodes);
            NodeContainer interim = visit(ctx.returnClause());
            returnContainer.validNodes.addAll(interim.validNodes);
        }
        nodeObj.validNodes = ogValidNodes;
        nodeObj.varBindings.put(ctx.VAR().getText(), returnContainer.validNodes);
        return returnContainer;
    }

    @Override
    public NodeContainer visitAttrList(XGrammarParser.AttrListContext ctx) {
        NodeContainer newAttrContainer = new NodeContainer(nodeObj);
        newAttrContainer.attrList = new ArrayList<>();
        int noOfAttrs = ctx.TAGNAME().size();
        for (int i = 0; i < noOfAttrs; i++) {
            newAttrContainer.attrList.add(ctx.TAGNAME().get(i).getText());
        }
        return newAttrContainer;
    }

    public ArrayList<Node> xqJoinHelper(XGrammarParser.XqJoinContext ctx, ArrayList<Node> combinedResults, int nestIndex, int nestDepth) {
        //base case
        if (nestIndex >= nestDepth){ //if the index of the current nested index is out of bounds, perform current return on current indices
            // The grammar file specifies * wildcard but there can only be 1 where keyword
            // Hence defaulting to index 0
            if (ctx.whereClause() != null){
                NodeContainer whereContainer = visit(ctx.whereClause()); //filters based on where condition
                if (whereContainer.validNodes.size() == 0) { //skip return
                    return combinedResults;
                }
            }
            combinedResults.addAll(visit(ctx.returnClause()).validNodes);
            return combinedResults;
        }
        String varName = ctx.forClause().VAR(nestIndex).getText(); //current variable to be moved and updated
        ArrayList<Node> toTraverse = new ArrayList<>(visit(ctx.forClause().xq(nestIndex)).validNodes); //evaluating xq at the position and copy the list
        for (Node aNode: toTraverse) {
            ArrayList<Node> arrayList = new ArrayList<>();
            arrayList.add(aNode);
            nodeObj.varBindings.put(varName, arrayList); //will replace any prior set value
            combinedResults = xqJoinHelper(ctx, combinedResults, nestIndex + 1, nestDepth);
        }
        return combinedResults;
    }
    @Override
    public NodeContainer visitXqJoin(XGrammarParser.XqJoinContext ctx) {
        //System.out.println("Entered xqjoin");
        // Base case
        HashMap<String, ArrayList<Node>> context0 = new HashMap<>();
        if (nodeObj != null) {
            context0 = nodeObj.varBindings; //save current scope
        }
        ArrayList<Node> toAdd = new ArrayList<>();
        ArrayList<Node> toReturn = new ArrayList<>();
        int nestDepth = ctx.forClause().VAR().size(); //total number of nested for loops
        int nestIndex = 0;
        toReturn = xqJoinHelper(ctx, toAdd, nestIndex, nestDepth);
        nodeObj.validNodes = toReturn;
        nodeObj.varBindings = context0;
        //NodeContainer nodeContainer = new NodeContainer();
        //nodeContainer.validNodes = toReturn;
        return nodeObj;
    }

    @Override
    public NodeContainer visitJoin(XGrammarParser.JoinContext ctx) {
       // System.out.println("Entering join method");

        ArrayList<Node> ogValidNodes;
        if (nodeObj != null) {
            ogValidNodes = new ArrayList<>(nodeObj.validNodes);
        } else {
            ogValidNodes = new ArrayList<>();
        }
        ArrayList<NodeContainer> joinTuples = new ArrayList<>();
        ArrayList<ArrayList<String>> ats = new ArrayList<>();
        int atListCtr = 0;
        for (int i=0; i<ctx.join().size(); i++) {
            // By my understanding, this will always execute only once for a given call to visitJoin
            joinTuples.add(new NodeContainer(visit(ctx.join(i))));
            ats.add(visit(ctx.attrList(atListCtr++)).attrList);
        }

        for (int i=0; i<ctx.xqJoin().size(); ++i) {
            // If the above has executed once, this will only execute once
            // Else it will execute twice
            joinTuples.add(new NodeContainer(visit(ctx.xqJoin(i))));
            ats.add(visit(ctx.attrList(atListCtr++)).attrList);
        }

        assert joinTuples.size() == 2;

        int sizeOfLeftTable = joinTuples.get(0).validNodes.size();
        int sizeOfRightTable = joinTuples.get(1).validNodes.size();

       // System.out.println(sizeOfLeftTable + " " + sizeOfRightTable);

        // Should always be 2 for the purpose of this project
        int nAttrs = ctx.attrList().size();
        if (nAttrs > 2)
            throw new RuntimeException("Unhandled case: More that 2 attribute lists passed");

        ArrayList<String> leftAttrList = ats.get(0);
        ArrayList<String> rightAttrList = ats.get(1);

        ArrayList<Node> joinResults = new ArrayList<>();
        if (sizeOfLeftTable < sizeOfRightTable) {
            // Hash on left table attrs
            joinResults = performJoin(joinTuples.get(0), joinTuples.get(1), leftAttrList, rightAttrList);
        } else {
            // Hash on right table attrs, swap the order
            joinResults = performJoin(joinTuples.get(1), joinTuples.get(0), rightAttrList, leftAttrList);
        }

        NodeContainer resultContainer = new NodeContainer();
        resultContainer.validNodes = joinResults;
//        return resultContainer;
        nodeObj.validNodes = ogValidNodes;
//        nodeObj.validNodes = joinResults;
        return resultContainer;

    }

    public ArrayList<Node> performJoin(NodeContainer leftResults, NodeContainer rightResults,
                                       ArrayList<String> leftAttrList, ArrayList<String> rightAttrList) {
        ArrayList<Node> joinResults = new ArrayList<>();

        // Create hashmap on attrList for the first table
        HashMap<String, ArrayList<Node>> hashIndex = new HashMap<>();
        for (Node node: leftResults.validNodes) {
            // Hash value key
            ArrayList<String> indexVals = createHash(node, leftAttrList);
            // add to the index
            if (hashIndex.containsKey(indexVals.toString()))
                // Handle hash collisions
                hashIndex.get(indexVals.toString()).add(node);
            else {
                ArrayList<Node> indexArrList = new ArrayList<>();
                indexArrList.add(node);
                hashIndex.put(indexVals.toString(), indexArrList);
            }
        }

        // Now perform join --> join right table with left based on hash
        for (Node node: rightResults.validNodes) {
            ArrayList<String> indexVals = createHash(node, rightAttrList);
//            System.out.println(boom++ + " " + indexVals);

            if (hashIndex.containsKey(indexVals.toString())) {
                // hash match

                // child elements from left table --> fetch from hash map
                // --> this could be a list of elements
                for (Node matchedNode: hashIndex.get(indexVals.toString())) {
                    Element joinedElement = nodeObj.rootDoc.createElement(node.getNodeName());
                    NodeList leftChildren = matchedNode.getChildNodes();
                    for (int i = 0; i < leftChildren.getLength(); ++i) {
                        joinedElement.appendChild(leftChildren.item(i).cloneNode(true));
                    }
                    // Now fetch elements from the right table
                    NodeList rightChildren = node.getChildNodes();
                    for (int i = 0; i < rightChildren.getLength(); i++) {
                        joinedElement.appendChild(rightChildren.item(i).cloneNode(true));
                    }
                    joinResults.add(joinedElement);
                }
            }
        }
//        System.out.println(joinResults.stream().map(Node::getTextContent).collect(Collectors.toList()));
        return joinResults;
    }

    public ArrayList<String> createHash(Node node, ArrayList<String> attrList) {
        // analogy: columns of a tuple
        NodeList childNodes = node.getChildNodes();

        // fetch values of columns that matches the join attr
        // This map will store node names and text content, so that we can
        // choose which columns will form the index
        // Needed since we need to maintain order based on leftAttrs
        HashMap<String, String> attrMap = new HashMap<>();
        for (int i = 0; i < childNodes.getLength(); i++) {
            if (attrList.contains(childNodes.item(i).getNodeName())) {
                // Nodename, value
                attrMap.put(childNodes.item(i).getNodeName(), childNodes.item(i).getTextContent());
            }
//            // Nodename, value
//            attrMap.put(childNodes.item(i).getNodeName(), childNodes.item(i).getTextContent());
        }

        // This entire list will form the index value for the current tuple
        ArrayList<String> indexVals = new ArrayList<>();
        for (String attr: attrList) {
            indexVals.add(attrMap.get(attr));
        }
        return indexVals;
    }

    @Override
    public NodeContainer visitJoin_concat(XGrammarParser.Join_concatContext ctx) {
        // this is a return clause case
        // Copy of xq_concat
        ArrayList<Node> og = new ArrayList<>();
        if (nodeObj != null) {
            og = nodeObj.validNodes;
        }

        NodeContainer rp1 = visit(ctx.returnClauseJoin(0)); //visit with current node object state
        ArrayList<Node> rp1List = new ArrayList<Node>(rp1.validNodes);

        // Since nodes that were added to validNodes from the first operand shouldn't be considered for the second operand
        nodeObj.validNodes = og;
        NodeContainer rp2 = visit(ctx.returnClauseJoin(1)); //visit with current node object state
        ArrayList<Node> rp2List = new ArrayList<Node>(rp2.validNodes);
        rp1List.addAll(rp2List);

        nodeObj.validNodes = rp1List;
        return removeDuplicates(nodeObj);
    }

    @Override
    public NodeContainer visitJoin_tag_no_constr(XGrammarParser.Join_tag_no_constrContext ctx) {
        // This is a join return clause case
        // copy of xq_tag
        NodeContainer nodeContainer = visit(ctx.returnClauseJoin()); //first visit xq then build
        // nodeObj.validNodes.addAll(nodeContainer.validNodes);
        nodeObj.validNodes = removeDuplicates(nodeContainer).validNodes;
        // nodeObj.validNodes = nodeContainer.validNodes; //TODO: CHECK
        String tagName = ctx.TAGNAME(0).getText();

        Node elementNode;

        if (nodeObj != null) {
            nodeObj.rootDoc.setXmlStandalone(true);
            elementNode = nodeObj.rootDoc.createElement(tagName);
        } else{
            //elementNode = nodeObj.rootDoc.createElement(tagName);
            DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder docBuilder = null;
            try {
                docBuilder = docFactory.newDocumentBuilder();
            } catch (ParserConfigurationException e) {
                throw new RuntimeException(e);
            }
            Document doc = docBuilder.newDocument();
            elementNode = doc.createTextNode(tagName);
        }

        for (Node node: nodeObj.validNodes){
            if (node != null) {
                elementNode.appendChild(nodeObj.rootDoc.importNode(node, true)); //append deep copy of node
            }
        }
        NodeContainer toReturn = new NodeContainer();
        toReturn.addElement(elementNode);
        return toReturn;
    }

    @Override
    public NodeContainer visitJoin_tag_constr(XGrammarParser.Join_tag_constrContext ctx) {
        // This is a return clause case
        // copy of xq_tag
        NodeContainer nodeContainer = visit(ctx.returnClauseJoin()); //first visit xq then build
        // nodeObj.validNodes.addAll(nodeContainer.validNodes);
        nodeObj.validNodes = removeDuplicates(nodeContainer).validNodes;
        // nodeObj.validNodes = nodeContainer.validNodes; //TODO: CHECK
        String tagName = ctx.TAGNAME(0).getText();

        Node elementNode;

        if (nodeObj != null) {
            nodeObj.rootDoc.setXmlStandalone(true);
            elementNode = nodeObj.rootDoc.createElement(tagName);
        } else{
            //elementNode = nodeObj.rootDoc.createElement(tagName);
            DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder docBuilder = null;
            try {
                docBuilder = docFactory.newDocumentBuilder();
            } catch (ParserConfigurationException e) {
                throw new RuntimeException(e);
            }
            Document doc = docBuilder.newDocument();
            elementNode = doc.createTextNode(tagName);
        }

        for (Node node: nodeObj.validNodes){
            if (node != null) {
                elementNode.appendChild(nodeObj.rootDoc.importNode(node, true)); //append deep copy of node
            }
        }
        NodeContainer toReturn = new NodeContainer();
        toReturn.addElement(elementNode);
        return toReturn;
    }

    @Override
    public NodeContainer visitJoin_ap(XGrammarParser.Join_apContext ctx) {
        // This is a join return clause case
        // copy of xq_ap
        visit(ctx.ap());
        return removeDuplicates(nodeObj);
    }

    @Override
    public NodeContainer visitJoin_return(XGrammarParser.Join_returnContext ctx) {
        // This is a  join return clause case
        // copy of xq_var
        NodeContainer nodeContainer = new NodeContainer();
        ArrayList<Node> getList = new ArrayList<>();
        String varName = ctx.VAR().getText();
        if (nodeObj.varBindings.containsKey(varName)) {
            getList = nodeObj.varBindings.get(varName);
        } else{
            System.out.println(varName);
            throw new RuntimeException("Invalid Var Name");
        }
        nodeContainer.validNodes = getList;
        return nodeContainer;
    }

    @Override
    public NodeContainer visitJoin_cond_var_eq(XGrammarParser.Join_cond_var_eqContext ctx) {
        // copy of cond_eq
        ArrayList<Node> og = new ArrayList<>();
        if (nodeObj != null) {
            og = nodeObj.validNodes;
        }
        //ArrayList<Node> og = new ArrayList<>(nodeObj.validNodes);
        ArrayList<Node> returnList = new ArrayList<>();

        for (Node ogNode : og) {
            nodeObj.validNodes = new ArrayList<>();
            nodeObj.validNodes.add(ogNode);


            // Logic from xq_var
            ArrayList<Node> op1 = new ArrayList<>();
            String varName1 = ctx.VAR(0).getText();
            if (nodeObj.varBindings.containsKey(varName1)) {
                op1 = nodeObj.varBindings.get(varName1);
            } else{
                System.out.println(varName1);
                throw new RuntimeException("Invalid Var Name");
            }


            nodeObj.validNodes = new ArrayList<>();
            nodeObj.validNodes.add(ogNode);


            // Logic from xq_var
            ArrayList<Node> op2 = new ArrayList<>();
            String varName2 = ctx.VAR(1).getText();
            if (nodeObj.varBindings.containsKey(varName2)) {
                op2 = nodeObj.varBindings.get(varName2);
            } else{
                System.out.println(varName2);
                throw new RuntimeException("Invalid Var Name");
            }


            for (Node node1 : op1) {
                for (Node node2 : op2) {
                    if (node1.isEqualNode(node2)) {
                        returnList.add(ogNode);
                    }
                }
            }
        }
        NodeContainer toReturn = new NodeContainer();
        toReturn.validNodes = returnList;
        return toReturn;
    }

    @Override
    public NodeContainer visitJoin_cond_var_const_eq(XGrammarParser.Join_cond_var_const_eqContext ctx) {
        // copy of cond_eq
        ArrayList<Node> og = new ArrayList<>();
        if (nodeObj != null) {
            og = nodeObj.validNodes;
        }
        //ArrayList<Node> og = new ArrayList<>(nodeObj.validNodes);
        ArrayList<Node> returnList = new ArrayList<>();

        for (Node ogNode : og) {
            nodeObj.validNodes = new ArrayList<>();
            nodeObj.validNodes.add(ogNode);


            // Logic from xq_var
            ArrayList<Node> op1 = new ArrayList<>();
            String varName1 = ctx.VAR().getText();
            if (nodeObj.varBindings.containsKey(varName1)) {
                op1 = nodeObj.varBindings.get(varName1);
            } else{
                System.out.println(varName1);
                throw new RuntimeException("Invalid Var Name");
            }


            nodeObj.validNodes = new ArrayList<>();
            nodeObj.validNodes.add(ogNode);


            String varName2 = ctx.STRINGCONST().getText();
            varName2 = varName2.substring(1, varName2.length()-1); //ignore quotes

            for (Node node1 : op1) {
                if (node1.getTextContent().equals(varName2)) {
                    returnList.add(ogNode);
                }
            }
        }
        NodeContainer toReturn = new NodeContainer();
        toReturn.validNodes = returnList;
        return toReturn;
    }

    @Override
    public NodeContainer visitJoin_cond_const_eq(XGrammarParser.Join_cond_const_eqContext ctx) {
        // copy of cond_eq
        ArrayList<Node> og = new ArrayList<>();
        if (nodeObj != null) {
            og = nodeObj.validNodes;
        }
        //ArrayList<Node> og = new ArrayList<>(nodeObj.validNodes);
        ArrayList<Node> returnList = new ArrayList<>();

        for (Node ogNode : og) {

            String varName1 = ctx.STRINGCONST(0).getText();
            varName1 = varName1.substring(1, varName1.length()-1); //ignore quotes

            String varName2 = ctx.STRINGCONST(1).getText();
            varName2 = varName2.substring(1, varName2.length()-1); //ignore quotes

            if (varName1.equals(varName2)) {
                returnList.add(ogNode);
            }

        }
        NodeContainer toReturn = new NodeContainer();
        toReturn.validNodes = returnList;
        return toReturn;
    }

    @Override
    public NodeContainer visitJoin_cond_and(XGrammarParser.Join_cond_andContext ctx) {
        // copy of cond_and
        ArrayList<Node> og = new ArrayList<>();
        if (nodeObj != null) {
            og = nodeObj.validNodes;
        }
        //ArrayList<Node> og = new ArrayList<>(nodeObj.validNodes);
        ArrayList<Node> nodes0 = visit(ctx.condClauseJoin(0)).validNodes;
        nodeObj.validNodes = og;
        ArrayList<Node> nodes1 = visit(ctx.condClauseJoin(1)).validNodes;
        ArrayList<Node> result = new ArrayList<>();
        for (Node node : nodes0) {
            if (nodes1.contains(node)) {
                result.add(node);
            }
        }
        //nodeObj.validNodes = result;
        NodeContainer toReturn = new NodeContainer();
        toReturn.validNodes = result;
        return toReturn;
    }
}