edu.wpi.margrave.MCommunicator.java Source code

Java tutorial

Introduction

Here is the source code for edu.wpi.margrave.MCommunicator.java

Source

/*
 Copyright 2009-2010 Brown University and Worcester Polytechnic Institute.
    
This file is part of Margrave.
    
Margrave is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
    
Margrave is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.
    
You should have received a copy of the GNU Lesser General Public License
along with Margrave.  If not, see <http://www.gnu.org/licenses/>.
 */

package edu.wpi.margrave;

import kodkod.ast.*;

import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.io.output.WriterOutputStream;
import org.w3c.dom.Document;
//import org.w3c.dom.Element;
//import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

public class MCommunicator {
    static final InputStream in = System.in;
    static final PrintStream out = System.out;

    static String sLogFileName = "margrave-log.txt";
    static BufferedWriter outLog = null;
    static FileWriter outLogStream = null;

    static boolean bDoLogging = false;
    static boolean bMinimalModels = false;

    /**
     * Creates a fatal error with the appropriate message inside.
     * @param str
     * @return
     */
    static String makeDetailedError(String str) {
        return "<MARGRAVE-RESPONSE type=\"fatal-error\"><ERROR>" + str + "</ERROR></MARGRAVE-RESPONSE>";
    }

    public static void main(String[] args) {
        ArrayList<String> foo = new ArrayList<String>();

        Set<String> argsSet = new HashSet<String>();
        for (int ii = 0; ii < args.length; ii++) {
            argsSet.add(args[ii].toLowerCase());
        }

        if (argsSet.contains("-log")) {
            // parser is in racket now. instead, require -log switch for logging
            //MEnvironment.debugParser = true;
            bDoLogging = true;
        }

        if (argsSet.contains("-min")) {
            bMinimalModels = true;
        }

        // Re-direct all System.err input to our custom buffer
        // Uses Apache Commons IO for WriterOutputStream.
        System.setErr(new PrintStream(new WriterOutputStream(MEnvironment.errorWriter), true));

        // Re-direct all System.out input to our custom buffer.
        // (We have already saved System.out.)
        // This is useful in case we start getting GC messages from SAT4j.
        System.setOut(new PrintStream(new WriterOutputStream(MEnvironment.outWriter), true));

        initializeLog();
        writeToLog("\n\n");

        // Inform the caller that we are ready to receive commands
        sendReadyReply();

        while (true) {
            // Block until a command is received, handle it, and then return the result.
            handleXMLCommand(in);
        }

        // outLog will be closed as it goes out of scope
    }

    /**
     * Places a "success" message on the output buffer. This message
     * lets the caller (Racket) know that it is safe to begin sending
     * commands.
     *
     */
    public static void sendReadyReply() {
        Document theResponse = MEnvironment.successResponse();
        addBuffers(theResponse);
        writeToLog("Returning: " + transformXMLToString(theResponse) + "\n");
        try {
            out.write(transformXMLToByteArray(theResponse));
        } catch (IOException ex) {
            Logger.getLogger(MCommunicator.class.getName()).log(Level.SEVERE, null, ex);
            writeToLog(
                    "\nIOException in handleXMLCommand while parsing command stream: " + ex.getLocalizedMessage());
            System.exit(1);
        }
    }

    public static void handleXMLCommand(String str) {
        // For test cases only!
        InputStream theInput = new ByteArrayInputStream(str.getBytes());
        handleXMLCommand(theInput);
    }

    public static void handleXMLCommand(InputStream commandStream) {
        DocumentBuilder docBuilder = null;
        DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
        try {
            docBuilder = docFactory.newDocumentBuilder();
        } catch (ParserConfigurationException ex) {
            Logger.getLogger(MCommunicator.class.getName()).log(Level.SEVERE, null, ex);
            writeToLog(
                    "\nParserConfigurationException at beginning of handleXMLCommand: " + ex.getLocalizedMessage());
            writeToLog("\nTerminating engine...");
            System.exit(200);
        }

        // Save this command for use in exception messages
        //MEnvironment.lastCommandReceived = command.trim();

        Document inputXMLDoc = null;

        try {
            // doc = docBuilder.parse(new InputSource(new StringReader(command)));
            writeToLog("========================================\n========================================\n");
            writeToLog("\n" + commandStream.available());

            StringBuffer inputStringBuffer = new StringBuffer();
            while (true) {
                // if available=0; we want to block. But don't want to allocate too much room:
                if (commandStream.available() < 1) {
                    int b = commandStream.read();
                    inputStringBuffer.append((char) b);
                    //writeToLog("\n(Blocked and then got) character: `"+(char)b+"`");
                } else {
                    byte[] inputBytes = new byte[commandStream.available()];
                    @SuppressWarnings("unused")
                    int bytesRead = commandStream.read(inputBytes);
                    String block = new String(inputBytes);
                    inputStringBuffer.append(block);
                    //writeToLog("\n(Didn't block for) String: `"+block+"`");
                }

                // Bad kludge. Couldn't get proper XML parse function working, so
                // did this. Should re-write (preferably with correct XML handling functions!)
                // The trim() call below is especially egregious... - TN

                String sMargraveCommandEnding = "</MARGRAVE-COMMAND>";
                String bufferStr = inputStringBuffer.toString();
                if (bufferStr.trim().endsWith(sMargraveCommandEnding))
                    break;
            } // end while(true) for kludge

            String cmdString = inputStringBuffer.toString();
            writeToLog("\n\n*********************************\nDONE! Received command: `" + cmdString + "`\n");

            inputXMLDoc = docBuilder.parse(new InputSource(new StringReader(cmdString)));

            //doc = docBuilder.parse(commandStream);
            //doc = docBuilder.parse(new InputSource(commandStream));
            writeToLog((new Date()).toString());
            writeToLog("\nExecuting command: " + transformXMLToString(inputXMLDoc) + "\n");
        } // end try
        catch (SAXException ex) {
            Logger.getLogger(MCommunicator.class.getName()).log(Level.SEVERE, null, ex);
            writeToLog(
                    "\nSAXException in handleXMLCommand while parsing command stream: " + ex.getLocalizedMessage());
        } catch (IOException ex) {
            Logger.getLogger(MCommunicator.class.getName()).log(Level.SEVERE, null, ex);
            writeToLog(
                    "\nIOException in handleXMLCommand while parsing command stream: " + ex.getLocalizedMessage());
        }

        /////////////////////////////////////////////////////
        // Done parsing input. Now prepare the response.

        Document theResponse;
        try {
            // protect against getFirstChild() call
            if (inputXMLDoc != null)
                theResponse = xmlHelper(inputXMLDoc.getFirstChild(), "");
            else
                theResponse = MEnvironment.errorResponse(MEnvironment.sNotDocument, MEnvironment.sCommand, "");
        } catch (Exception e) {
            // Construct an exception response;
            theResponse = MEnvironment.exceptionResponse(e);
        } catch (Throwable e) {
            // This would ordinarily be a terrible thing to do (catching Throwable)
            // However, we need to warn the client that we're stuck.

            try {
                // First log that we got an exception:
                writeToLog("\n~~~ Throwable caught: " + e.getClass());
                writeToLog("\n    " + e.getLocalizedMessage());
                writeToLog("\n" + Arrays.toString(e.getStackTrace()));
                writeToLog("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
                theResponse = MEnvironment.exceptionResponse(e);
            } catch (Throwable f) {
                // If we can't even warn the client, at least close down the engine. Client will detect EOF.
                theResponse = null;
                System.exit(101);
            }

            // Last resort
            System.exit(100);
        }

        try {
            addBuffers(theResponse);

            writeToLog("Returning: " + transformXMLToString(theResponse) + "\n");
            out.write(transformXMLToByteArray(theResponse));
        } catch (IOException e) {
            // don't do this. would go through System.err
            //e.printStackTrace();
        }
        out.flush(); // ALWAYS FLUSH!

    } // end handleXMLCommand

    /**
     * We suspended stdin and stdout. Append what has accumulated to the response.
     * @param theResponse
     */
    protected static void addBuffers(Document theResponse) {
        // add in any supplemental or error information
        Element envOutChild = theResponse.createElementNS(null, "EXTRA-OUT");
        envOutChild.appendChild(theResponse.createTextNode(MEnvironment.outBuffer.toString()));
        Element envErrChild = theResponse.createElementNS(null, "EXTRA-ERR");
        envErrChild.appendChild(theResponse.createTextNode(MEnvironment.errorBuffer.toString()));

        // Clear out the "out" and "error" buffers.
        MEnvironment.errorBuffer.getBuffer().setLength(0);
        MEnvironment.outBuffer.getBuffer().setLength(0);

        theResponse.getDocumentElement().appendChild(envOutChild);
        theResponse.getDocumentElement().appendChild(envErrChild);
    }

    /**
     * Takes a MARGRAVE-COMMAND node and performs the appropriate action.
     *
     * @param margraveCommandNode
     * @param originalXMLText
     * @return
     * @throws MBaseException
     */
    private static Document xmlHelper(Node margraveCommandNode, String originalXMLText) throws MBaseException {
        //List<Node> childNodes = getElementChildren(node);

        writeToLog("\n  In XML Helper...");

        Document theResponse = null;

        if (margraveCommandNode.getNodeType() == Node.ELEMENT_NODE) {
            String type = getNodeAttribute(margraveCommandNode, "type");
            writeToLog("  At command node. Type attribute was: " + type + "\n");
            if (type == null)
                return MEnvironment.errorResponse(MEnvironment.sUnknown, MEnvironment.sCommand,
                        "<null command type>");

            if (type.equalsIgnoreCase("SET")) {
                Node setNode = getChildNode(margraveCommandNode, "SET");

                String setParameter = getNodeAttribute(setNode, "parameter");
                String setSubParameter = getNodeAttribute(setNode, "subparameter");
                String setValue = getNodeAttribute(setNode, "value");

                if ("CEILING".equalsIgnoreCase(setParameter)) {
                    try {
                        int intValue = Integer.parseInt(setValue);
                        // Global parameters for size ceilings per sort
                        theResponse = MEnvironment.setSortCeiling(setSubParameter, intValue);

                    } catch (NumberFormatException e) {
                        theResponse = MEnvironment.errorResponse(MEnvironment.sNotDocument, "Not an integer",
                                setValue);
                    }
                } else {
                    theResponse = MEnvironment.errorResponse(MEnvironment.sCommand, MEnvironment.sUnknown,
                            setParameter);
                }

            } else if (type.equalsIgnoreCase("EXPLORE")) {
                theResponse = handleExplore(originalXMLText, margraveCommandNode);
            } else if (type.equalsIgnoreCase("INFO")) {
                writeToLog("In Info");
                String idString = getInfoId(margraveCommandNode);
                writeToLog("\nPast getting id info");
                if (idString != null && idString.length() > 0) {
                    theResponse = MEnvironment.printInfo(idString);
                } else {
                    theResponse = MEnvironment.printSystemInfo();
                }
                writeToLog("Returning from info");
            }

            //Create Statement
            else if (type.equalsIgnoreCase("CREATE POLICY LEAF")) {
                String pname = getPolicyName(margraveCommandNode);
                String vname = getVocabName(margraveCommandNode);
                theResponse = MEnvironment.createPolicyLeaf(pname, vname);
            } else if (type.equalsIgnoreCase("CREATE POLICY SET")) {
                String pname = getPolicyName(margraveCommandNode);
                String vname = getVocabName(margraveCommandNode);
                theResponse = MEnvironment.createPolicySet(pname, vname);
            } else if (type.equalsIgnoreCase("CREATE VOCABULARY")) {
                writeToLog("In Create Vocabulary\n");
                String vname = getVocabName(margraveCommandNode);
                writeToLog("Got Vocab name. It is: " + vname + "\n");
                theResponse = MEnvironment.createVocabulary(vname);
                if (theResponse == null) {
                    writeToLog("The result of create vocabulary was null!!\n");
                }
                writeToLog("Finished Create Vocabulary\n");
            } else if (type.equalsIgnoreCase("LOAD XACML POLICY")) {
                String pname = getPolicyName(margraveCommandNode);
                String fileName = getLoadFileName(margraveCommandNode);
                String schemaFileName = getLoadSchemaFileName(margraveCommandNode);
                writeToLog("\n Loading policy in file: " + fileName + "; id to use: " + pname);
                theResponse = MEnvironment.loadXACML(pname, fileName, schemaFileName);
            } else if (type.equalsIgnoreCase("LOAD SQS POLICY")) {
                String pname = getPolicyName(margraveCommandNode);
                String fileName = getLoadFileName(margraveCommandNode);
                writeToLog("\n Loading policy in file: " + fileName + "; id to use: " + pname);
                theResponse = MEnvironment.loadSQS(pname, fileName);
            }

            else if (type.equalsIgnoreCase("PREPARE")) {
                String pname = getPolicyName(margraveCommandNode);
                theResponse = MEnvironment.preparePolicy(pname);
            } else if (type.equalsIgnoreCase("DELETE VOCABULARY")) {
                String vname = getVocabName(margraveCommandNode);
                theResponse = MEnvironment.deleteVocabulary(vname);
            }

            else if (type.equalsIgnoreCase("SET TARGET FOR POLICY")) {
                String pname = getPolicyName(margraveCommandNode);

                // Target fmla
                Node targetNode = getChildNode(margraveCommandNode, "TARGET");
                assert (targetNode != null);
                MExploreCondition targetCondition = exploreHelper(targetNode.getFirstChild(), null);
                Formula target = targetCondition.fmla;

                theResponse = MEnvironment.setPolicyTarget(pname, target);
            } else if (type.equalsIgnoreCase("SET COMBINE FOR POLICY")) {
                String pname = getPolicyName(margraveCommandNode);
                Set<String> rFA = new HashSet<String>();
                Map<String, Set<String>> rO = new HashMap<String, Set<String>>();

                handleComb(margraveCommandNode, rFA, rO);
                theResponse = MEnvironment.setCombine(pname, rFA, rO);
            } else if (type.equalsIgnoreCase("QUIT")) {
                MEnvironment.quitMargrave();
            }

            else if (type.equalsIgnoreCase("RESET")) {
                // "<MARGRAVE-COMMAND type=\"RESET\"><RESET id=\"Myqry\" /></MARGRAVE-COMMAND>"
                String id = getAttributeOfChildNodeOrNode(margraveCommandNode, "RESET", "id");
                theResponse = MEnvironment.resetIterator(id);
            } else if (type.equalsIgnoreCase("IS-POSSIBLE")) {
                String id = getAttributeOfChildNodeOrNode(margraveCommandNode, "IS-POSSIBLE", "id");
                theResponse = MEnvironment.isPoss(id);
                writeToLog("Returning from IS-POSSIBLE");
            }
            /*else if (type.equalsIgnoreCase("IS-GUARANTEED")) {
             String id = getAttributeOfChildNodeOrNode(margraveCommandNode, "IS-GUARANTEED", "id");
             theResponse = MEnvironment.isGuar(id);
                 }*/
            else if (type.equalsIgnoreCase("SHOW")) {
                writeToLog("In show");
                String showType = getShowType(margraveCommandNode);
                writeToLog("In show");
                String id = getAttributeOfChildNodeOrNode(margraveCommandNode, "SHOW", "id");

                Node showNode = getChildNode(margraveCommandNode, "SHOW");

                writeToLog("\nshowtype: " + showType + "\n");
                if (showType.equalsIgnoreCase("ONE")) {
                    writeToLog("\nIn SHOW ONE:");

                    Node includeNode = getIncludeNode(showNode);
                    List<Node> idbChildNodes = getElementChildren(includeNode);
                    HashMap<String, Set<List<MTerm>>> includeMap = atomicFormulasToHashmap(idbChildNodes);

                    try {
                        writeToLog("\n  id was: " + id + "; includeMap was: " + includeMap
                                + "; idbChildNodes had this many elements: " + idbChildNodes.size());
                        theResponse = MEnvironment.getFirstModel(id, includeMap);
                    } catch (MBaseException e) {
                        theResponse = MEnvironment.exceptionResponse(e);
                    }
                } else if (showType.equalsIgnoreCase("NEXT")) {
                    writeToLog("\nIn SHOW NEXT:");

                    Node includeNode = getIncludeNode(showNode);
                    List<Node> idbChildNodes = getElementChildren(includeNode);
                    HashMap<String, Set<List<MTerm>>> includeMap = atomicFormulasToHashmap(idbChildNodes);

                    try {
                        writeToLog("\n  id was: " + id + "; includeMap was: " + includeMap
                                + "; idbChildNodes had this many elements: " + idbChildNodes.size());
                        theResponse = MEnvironment.getNextModel(id, includeMap);
                    } catch (MBaseException e) {
                        theResponse = MEnvironment.exceptionResponse(e);
                    }
                }
                /*else if (showType.equalsIgnoreCase("CEILING")) {
                theResponse = MEnvironment.showCeiling(id);
                     }*/
                else if (showType.equalsIgnoreCase("REALIZED") || showType.equalsIgnoreCase("UNREALIZED")) {
                    String popId;
                    if (showType.equalsIgnoreCase("REALIZED")) {
                        popId = getPopulatedId(margraveCommandNode); // command
                    } else {
                        popId = getUnpopulatedId(margraveCommandNode); // command
                    }

                    Node forCasesNode = getForCasesNode(showNode);
                    List<Node> atomicFormulaNodes = getElementChildren(showNode);

                    // not this call
                    //NodeList atomicFormulaNodes = getAtomicFormulaNodesFromList(n);

                    // atomicFormulasToHashmap will ignore the FOR-CASES element.
                    Map<String, Set<List<MTerm>>> atomicFormulas = atomicFormulasToHashmap(atomicFormulaNodes);

                    // Default map is empty. If FOR CASES, populate it.
                    Map<String, Set<List<MTerm>>> forCasesAtomicFormulas = new HashMap<String, Set<List<MTerm>>>();
                    if (forCasesNode != null) {
                        //NodeList forCasesAtomicFormulaNodes = getAtomicFormulaNodesFromList(forCasesNode);
                        List<Node> forCasesAtomicFormulaNodes = getElementChildren(forCasesNode);
                        forCasesAtomicFormulas = atomicFormulasToHashmap(forCasesAtomicFormulaNodes);
                    }

                    //writeToLog(showType+" " + popIdString + " " + atomicFormulas + " --- " + forCasesAtomicFormulas);

                    // Get the result and return it
                    if (showType.equalsIgnoreCase("REALIZED")) {
                        theResponse = MEnvironment.showRealized(popId, atomicFormulas, forCasesAtomicFormulas);
                    } else {
                        theResponse = MEnvironment.showUnrealized(popId, atomicFormulas, forCasesAtomicFormulas);
                    }

                } // end pop/unpop

            } // end show
            else if (type.equalsIgnoreCase("COUNT")) {
                String id = getCountId(margraveCommandNode);
                theResponse = MEnvironment.countModels(id);

                Node sizeNode = getSizeNode(margraveCommandNode);
                if (sizeNode != null) {
                    Integer countSize = Integer.parseInt(getCountSize(sizeNode));
                    theResponse = MEnvironment.countModels(id, countSize);
                } else {
                    theResponse = MEnvironment.countModels(id);
                }
            }

            else if (type.equalsIgnoreCase("GET-INFO")) {
                // n is the margrave-command node.
                String getType = getGetInfoType(margraveCommandNode);
                Node getNode = getChildNode(margraveCommandNode, "GET-INFO");
                String pname = getPolicyName(getNode);
                String decname = getDecisionName(getNode);

                // decname will be null, if the user doesn't want to limit by decision

                writeToLog("\n");
                writeToLog("GET-INFO: " + getType + " " + getNode + " " + pname + " " + decname);

                String rname = "";

                if (getType.equalsIgnoreCase("HIGHER-PRIORITY-THAN")) {
                    theResponse = MEnvironment.getHigherPriorityThan(pname, rname);
                } else if (getType.equalsIgnoreCase("RULES")) {
                    theResponse = MEnvironment.getRulesIn(pname, false, decname);
                } else if (getType.equalsIgnoreCase("QUALIFIED-RULES")) {
                    theResponse = MEnvironment.getRulesIn(pname, true, decname);
                }
            }

            //Add Statement
            else if (type.equalsIgnoreCase("ADD")) {

                Node childNode = margraveCommandNode.getFirstChild();

                if (childNode.getNodeName().equalsIgnoreCase("VOCAB-IDENTIFIER")) {
                    theResponse = handleAddVocabFact(margraveCommandNode, childNode);
                } else if (childNode.getNodeName().equalsIgnoreCase("POLICY-IDENTIFIER")) {
                    String pname = getPolicyName(margraveCommandNode);

                    Node secondChildNode = childNode.getNextSibling(); // WORRY Shouldn't be hardcoded in!!
                    if (secondChildNode.getNodeName().equalsIgnoreCase("RULE")) {
                        String rname = getRuleName(margraveCommandNode);

                        // WORRY This should be changed to be made more generic, because it assumes too much
                        Node ruleNode = childNode.getNextSibling();

                        // Decision
                        String decName = getDecisionType(ruleNode);

                        List<String> varOrdering = new ArrayList<String>();
                        varOrdering = getListElements(ruleNode, "DECISION-TYPE", "id");

                        // Target fmla
                        Node targetNode = getChildNode(ruleNode, "TARGET");
                        assert (targetNode != null);
                        MExploreCondition targetCondition = exploreHelper(targetNode.getFirstChild(),
                                new ArrayList<String>());
                        Formula target = targetCondition.fmla;

                        // condition is used exclusively for XACML, so it's ignored here.
                        Formula condition = Formula.TRUE;
                        theResponse = MEnvironment.addRule(pname, rname, decName, varOrdering, target, condition,
                                targetCondition);
                    } else if (secondChildNode.getNodeName().equalsIgnoreCase("VARIABLE-DECLARATION")) {
                        String varname = getAttributeOfChildNodeOrNode(secondChildNode, "VARIABLE-DECLARATION",
                                "varname");
                        String vartype = getAttributeOfChildNodeOrNode(secondChildNode, "VARIABLE-DECLARATION",
                                "sort");

                        theResponse = MEnvironment.addVarDec(pname, varname, vartype);
                    }

                } else if (childNode.getNodeName().equalsIgnoreCase("PARENT")) {
                    // Declaring that <parent> is a policy set with a new child <child>
                    String parent = getParentName(childNode);
                    String child = getChildName(childNode);
                    theResponse = MEnvironment.addChild(parent, child);
                }
            } else {
                theResponse = MEnvironment.errorResponse(MEnvironment.sUnknown, MEnvironment.sCommand, "");
            }
        } else {
            theResponse = MEnvironment.errorResponse(MEnvironment.sNotDocument, "", "");
        }

        // Finally -- did we assign a proper response? If not, none of the above cases was matched.
        // _SHOULD_ have elses everywhere. This is just in case.
        if (theResponse == null)
            theResponse = MEnvironment.errorResponse(MEnvironment.sFailure, "",
                    "Fatal error: No matching case in java engine. (If you receive this error, please notify the Margrave developers.)");

        return theResponse;
    }

    private static Document handleAddVocabFact(Node margraveCommandNode, Node childNode) {
        String vname = getVocabName(margraveCommandNode);
        Node secondChildNode = childNode.getNextSibling(); // WORRY Shouldn't be hardcoded in!!
        String addType = secondChildNode.getNodeName();
        writeToLog("addType: " + addType + "\n");

        if (addType.equalsIgnoreCase("SUBSORT")) {
            String parent = getSubSortParent(margraveCommandNode);
            String child = getSubSortChild(margraveCommandNode);
            return MEnvironment.addSubsort(vname, parent, child);
        } else if (addType.equalsIgnoreCase("SORT")) {
            String sortName = getSortName(margraveCommandNode);
            return MEnvironment.addSort(vname, sortName);
        } else if (addType.equalsIgnoreCase("SORT-WITH-CHILDREN")) {
            String sortName = getAttributeOfChildNodeOrNode(margraveCommandNode, "SORT-WITH-CHILDREN", "name");

            List<String> childnames = getListElements(margraveCommandNode, "SORT-WITH-CHILDREN", "name");
            return MEnvironment.addSortWithSubs(vname, sortName, childnames);
        } else if (addType.equalsIgnoreCase("PREDICATE")) {
            String sName = getPredicateName(margraveCommandNode);
            List<String> constr = getRelationsList(margraveCommandNode);
            writeToLog("Adding Predicate\n");
            return MEnvironment.addPredicate(vname, sName, constr);
        }

        else if (addType.equalsIgnoreCase("CONSTANT")) {
            String sName = getAttributeOfChildNodeOrNode(secondChildNode, "CONSTANT", "name");
            String sSort = getAttributeOfChildNodeOrNode(secondChildNode, "CONSTANT", "type");
            writeToLog("Adding constant " + sName + " : " + sSort + "\n");
            return MEnvironment.addConstant(vname, sName, sSort);
        } else if (addType.equalsIgnoreCase("FUNCTION")) {
            String sName = getAttributeOfChildNodeOrNode(secondChildNode, "FUNCTION", "name");
            List<String> constr = getListElements(secondChildNode, "RELATIONS", "name");
            writeToLog("Adding function " + sName + " : " + constr + "\n");
            //System.err.println("Adding function "+sName+" : "+constr+"\n");
            return MEnvironment.addFunction(vname, sName, constr);
        }

        else if (addType.equalsIgnoreCase("CONSTRAINT")) {
            Node constraintNode = secondChildNode; //Just for clarity

            String constraintType = getConstraintType(constraintNode);

            // FORMULA is special.
            if (constraintType.equalsIgnoreCase("FORMULA")) {
                return MEnvironment.addConstraintFormula(vname,
                        exploreHelper(constraintNode.getFirstChild(), new ArrayList<String>()).fmla);
            }

            List<String> relations = getRelationsList(constraintNode);

            assert (relations != null);
            String firstRelation = relations.get(0);

            if (constraintType.equalsIgnoreCase("SINGLETON")) {
                return MEnvironment.addConstraintSingleton(vname, firstRelation);
            } else if (constraintType.equalsIgnoreCase("SINGLETON-ALL")) {
                return MEnvironment.addConstraintSingletonAll(vname, firstRelation);
            } else if (constraintType.equalsIgnoreCase("ATMOSTONE")) {
                return MEnvironment.addConstraintAtMostOne(vname, firstRelation);
            } else if (constraintType.equalsIgnoreCase("DISJOINT")) {
                assert (relations.size() == 2);
                String secondRelation = relations.get(1);
                return MEnvironment.addConstraintDisjoint(vname, firstRelation, secondRelation);
            }

            else if (constraintType.equalsIgnoreCase("SUBSET")) {
                assert (relations.size() == 2);
                String secondRelation = relations.get(1);
                return MEnvironment.addConstraintSubset(vname, firstRelation, secondRelation);
            } else if (constraintType.equalsIgnoreCase("CONSTANTS-NEQ")) {
                assert (relations.size() == 2);
                String secondRelation = relations.get(1);
                return MEnvironment.addConstraintConstantsNeq(vname, firstRelation, secondRelation);
            } else if (constraintType.equalsIgnoreCase("CONSTANTS-COVER")) {
                return MEnvironment.addConstraintConstantsCover(vname, firstRelation);
            } else if (constraintType.equalsIgnoreCase("CONSTANTS-NEQ-ALL")) {
                MCommunicator.writeToLog("\nAdding constraint constants-neq-all: " + firstRelation);
                return MEnvironment.addConstraintConstantsNeqAll(vname, firstRelation);
            } else if (constraintType.equalsIgnoreCase("ATMOSTONE-ALL")) {
                return MEnvironment.addConstraintAtMostOneAll(vname, firstRelation);
            } else if (constraintType.equalsIgnoreCase("NONEMPTY")) {
                return MEnvironment.addConstraintNonempty(vname, firstRelation);
            } else if (constraintType.equalsIgnoreCase("NONEMPTY-ALL")) {
                return MEnvironment.addConstraintNonemptyAll(vname, firstRelation);
            } else if (constraintType.equalsIgnoreCase("ABSTRACT")) {
                return MEnvironment.addConstraintAbstract(vname, firstRelation);
            } else if (constraintType.equalsIgnoreCase("ABSTRACT-ALL")) {
                return MEnvironment.addConstraintAbstractAll(vname, firstRelation);
            } else if (constraintType.equalsIgnoreCase("TOTAL-FUNCTION")) {
                return MEnvironment.addConstraintTotalFunction(vname, firstRelation);
            } else if (constraintType.equalsIgnoreCase("TOTAL-RELATION")) {
                return MEnvironment.addConstraintTotalRelation(vname, firstRelation);
            } else if (constraintType.equalsIgnoreCase("PARTIAL-FUNCTION")) {
                return MEnvironment.addConstraintPartialFunction(vname, firstRelation);
            } else {
                // Unknown constraint type; throw an exception
                return MEnvironment.errorResponse(MEnvironment.sUnknown, MEnvironment.sConstraint, constraintType);
            }
        } else {
            return MEnvironment.errorResponse(MEnvironment.sCommand, MEnvironment.sNotExpected, addType);
        }
    }

    private static Document handleExplore(String originalXMLText, Node n) {
        MExploreCondition exploreCondition;

        // Catch and re-throw any exception, because if EXPLORE fails,
        // need to reset lastResult to -1.

        try {
            n = n.getFirstChild();
            String name = n.getNodeName();
            if (name.equalsIgnoreCase("EXPLORE")) {
                //Explore should only have one child - "Condition". exploreHelper takes the node one down from condition
                String queryID = getAttributeOfChildNodeOrNode(n, "EXPLORE", "id");

                if (MEnvironment.isQueryIDUsed(queryID)) {
                    // Don't allow ID re-use.
                    return MEnvironment.errorResponse(MEnvironment.sQuery, MEnvironment.sFailure,
                            "The query identifier " + queryID + " is already in use.");
                }

                MQuery result = null;

                //Default Values
                List<MIDBCollection> under = new LinkedList<MIDBCollection>();
                List<String> publ = new ArrayList<String>();
                Map<String, String> publSorts = new HashMap<String, String>();
                Integer debugLevel = 0;

                Node underNode = getUnderNode(n);
                Node publishNode = getPublishNode(n);
                Node debugNode = getDebugNode(n);
                Node ceilingsNode = getCeilingsNode(n);

                if (underNode != null) {
                    under = namesToIDBCollections(getUnderList(n));

                }
                if (publishNode != null) {
                    // <PUBLISH><VARIABLE-DECLARATION sort=\"B\"><VARIABLE-TERM id=\"y\" /></VARIABLE-DECLARATION>
                    //          <VARIABLE-DECLARATION sort=\"C\"><VARIABLE-TERM id=\"x\" /></VARIABLE-DECLARATION></PUBLISH>

                    List<Node> decls = getElementChildren(publishNode);
                    for (Node varDeclNode : decls) {
                        String varname = getAttributeOfChildNodeOrNode(varDeclNode, "VARIABLE-DECLARATION",
                                "varname");
                        String vartype = getAttributeOfChildNodeOrNode(varDeclNode, "VARIABLE-DECLARATION", "sort");
                        publ.add(varname);
                        publSorts.put(varname, vartype);
                    }

                }
                if (debugNode != null) {
                    debugLevel = Integer.parseInt(getDebugLevel(debugNode));
                }

                Map<String, Integer> ceilingMap = new HashMap<String, Integer>();
                if (ceilingsNode != null) {
                    List<Node> ceilingNodes = getElementChildren(ceilingsNode);

                    for (Node ceil : ceilingNodes) {
                        // LOCAL ceilings (for this query only)
                        String sortname = getNodeAttribute(ceil, "sort");
                        String ceiling = getNodeAttribute(ceil, "value");
                        writeToLog("\nCEILING: " + sortname + " is " + ceiling);

                        try {
                            int intValue = Integer.parseInt(ceiling);
                            ceilingMap.put(sortname, intValue);
                        } catch (NumberFormatException e) {
                            return MEnvironment.errorResponse(MEnvironment.sNotDocument, "Not an integer", ceiling);
                        }
                    }
                }

                // Finally extract the explore condition. Do last because need to gather
                // info from the other nodes (list of published vars).
                exploreCondition = exploreHelper(n.getFirstChild().getFirstChild(), new ArrayList<String>(publ));
                if (exploreCondition == null)
                    throw new MCommunicatorException("Explore condition is null!");

                // Exception will be thrown and caught by caller to return an EXCEPTION element.
                result = MQuery.createFromExplore(queryID, exploreCondition.addSeenIDBCollections(under), publ,
                        publSorts, debugLevel, ceilingMap);

                writeToLog("AT END OF EXPLORE");
                return MEnvironment.returnQueryResponse(result, originalXMLText);

            } // end if explore node

            throw new MUserException("Internal error: Command promised a query definition but did not provide it.");

        } catch (MBaseException e) {
            MEnvironment.clearLastQuery();
            throw e;
        }
    }

    private static void handleComb(Node n, Set<String> rFA, Map<String, Set<String>> rO) {
        Node combListNode = getChildNode(n, "COMB-LIST");
        List<Node> combNodes = getElementChildren(combListNode);
        for (Node combNode : combNodes) {
            if ("FA".equalsIgnoreCase(combNode.getNodeName())) {
                List<String> names = getListElements(combListNode, "FA", "id");
                rFA.addAll(names);
            } else if ("OVERRIDES".equalsIgnoreCase(combNode.getNodeName())) {
                String under = getAttributeOfChildNodeOrNode(combNode, "OVERRIDES", "decision");
                List<String> overs = getListElements(combListNode, "OVERRIDES", "id");
                if (!rO.containsKey(under))
                    rO.put(under, new HashSet<String>());
                rO.get(under).addAll(overs);
            }
            // else do nothing
        }

    }

    private static HashMap<String, Set<List<MTerm>>> atomicFormulasToHashmap(List<Node> childNodes) {
        //HashMap<String, Set<List<String>>> hashMap = new HashMap<String, Set<List<String>>>();

        // Used to be just variables:
        // R(x, y), P(y, z), R(z, z) --->
        // [ R->[["x","y"], ["z","z"]] P->[["y","z"]]
        // Now we have terms as well.

        HashMap<String, Set<List<MTerm>>> hashMap = new HashMap<String, Set<List<MTerm>>>();

        for (Node childNode : childNodes) {
            if (childNode.getNodeName().equalsIgnoreCase("FORCASES")) {
                // Ignore. This is a special child node for SHOW REALIZED.
            } else if (childNode.getNodeName().equalsIgnoreCase("ATOMIC-FORMULA")) {
                List<String> relNamePieces = getRelationNameFromAtomicFmla(childNode);
                List<MTerm> terms = getTermsFromAtomicFmla(childNode, null);

                String relName = "";
                for (int ii = 0; ii < relNamePieces.size(); ii++) {
                    // Re-compose dotted notation (for now)
                    if ("".equals(relName))
                        relName += relNamePieces.get(ii);
                    else
                        relName += MEnvironment.sIDBSeparator + relNamePieces.get(ii);
                }

                if (!hashMap.containsKey(relName))
                    hashMap.put(relName, new HashSet<List<MTerm>>());
                hashMap.get(relName).add(terms);

            } else if (childNode.getNodeName().equalsIgnoreCase("ISA")) {
                // Only atomic ISA allowed!
                String relName = getAttributeOfChildNodeOrNode(childNode, "ISA", "sort");

                // ISA has a special TERM sub-node. The first (and only) child of that is the variable-term, function-term, etc.
                MTerm theTerm = termHelper(getChildNode(childNode, "TERM").getFirstChild(), null);
                // ISA has a special FORMULA sub-node. The first (and only) child of that is the actual formula.
                MExploreCondition cond = exploreHelper(getChildNode(childNode, "FORMULA").getFirstChild(), null);

                if (theTerm == null || cond == null || !cond.fmla.equals(Formula.TRUE)) {
                    throw new MUnsupportedFormulaException(
                            "atomicFormulasToHashmap: Got a non-atomic ISA node. ISAs in this context must be atomic.");
                }

                if (!hashMap.containsKey(relName))
                    hashMap.put(relName, new HashSet<List<MTerm>>());
                List<MTerm> singletonList = new ArrayList<MTerm>(1);
                singletonList.add(theTerm);
                hashMap.get(relName).add(singletonList);

            } else if (childNode.getNodeName().equalsIgnoreCase("EQUALS")) {
                List<MTerm> terms = getTermsFromEqualsFmla(childNode, null);

                if (!hashMap.containsKey("="))
                    hashMap.put("=", new HashSet<List<MTerm>>());
                hashMap.get("=").add(terms);
            } else {
                throw new MUnsupportedFormulaException(
                        "atomicFormulasToHashmap: Expected a list of atomic formulas. Got a node with name: "
                                + childNode.getNodeName());
            }
        }

        return hashMap;
    }

    private static String getInfoId(Node n) {
        // Return "" if id field is null
        try {
            return getAttributeOfChildNodeOrNode(n, "INFO", "id");
        } catch (MCommunicatorException e) {
            return "";
        }
    }

    //Helper functions for specific parts of commands
    private static String getPolicyName(Node n) {
        return getAttributeOfChildNodeOrNode(n, "POLICY-IDENTIFIER", "pname");
    }

    private static String getVocabName(Node n) {
        return getAttributeOfChildNodeOrNode(n, "VOCAB-IDENTIFIER", "vname");
    }

    private static String getSubSortParent(Node n) {
        return getAttributeOfChildNodeOrNode(n, "SUBSORT", "parent");
    }

    private static String getSubSortChild(Node n) {
        return getAttributeOfChildNodeOrNode(n, "SUBSORT", "child");
    }

    private static String getSortName(Node n) {
        return getAttributeOfChildNodeOrNode(n, "SORT", "name");
    }

    private static String getPredicateName(Node n) {
        return getAttributeOfChildNodeOrNode(n, "PREDICATE", "name");
    }

    private static String getParentName(Node n) {
        return getAttributeOfChildNodeOrNode(n, "PARENT-IDENTIFIER", "name");
    }

    private static String getChildName(Node n) {
        return getAttributeOfChildNodeOrNode(n, "CHILD-IDENTIFIER", "name");
    }

    private static String getRuleName(Node n) {
        return getAttributeOfChildNodeOrNode(n, "RULE", "name");
    }

    private static String getDecisionName(Node n) {
        return getAttributeOfChildNodeOrNode(n, "DECISION", "name");
    }

    private static String getDecisionType(Node n) {
        return getAttributeOfChildNodeOrNode(n, "DECISION-TYPE", "type");
    }

    private static String getConstraintType(Node n) {
        return getAttributeOfChildNodeOrNode(n, "CONSTRAINT", "type");
    }

    //Rename
    /* public static String getRenameFirstId(Node n) {
       return getAttributeOfChildNodeOrNode(n, "RENAME", "id1");
    }
    public static String getRenameSecondId(Node n) {
       return getAttributeOfChildNodeOrNode(n, "RENAME", "id2");
    }*/

    //SHOW
    public static String getShowType(Node n) {
        return getAttributeOfChildNodeOrNode(n, "SHOW", "type");
    }

    public static Node getForCasesNode(Node n) {
        return getChildNode(n, "FORCASES");
    }

    // <SHOW type="populated" id="0" ...
    public static String getPopulatedId(Node n) {
        return getAttributeOfChildNodeOrNode(n, "SHOW", "id");
    }

    public static String getUnpopulatedId(Node n) {
        return getAttributeOfChildNodeOrNode(n, "SHOW", "id");
    }

    //COUNT
    private static String getCountId(Node n) {
        return getAttributeOfChildNodeOrNode(n, "COUNT", "id");
    }

    private static Node getSizeNode(Node n) {
        return getChildNode(n, "SIZE");
    }

    private static String getCountSize(Node n) {
        return getAttributeOfChildNodeOrNode(n, "COUNT", "size");
    }

    //GET
    public static String getGetInfoType(Node n) {
        return getAttributeOfChildNodeOrNode(n, "GET-INFO", "type");
    }

    //ATOMIC FORMULAS
    public static List<Node> getAtomicFormulaNodesFromList(Node n) {
        Node child = getChildNode(n, "ATOMIC-FORMULA-LIST");
        if (child == null)
            return null;
        return getElementChildren(child);
    }

    private static String getLoadFileName(Node n) {
        return getAttributeOfChildNodeOrNode(n, "LOAD", "file-name");
    }

    private static String getLoadSchemaFileName(Node n) {
        return getAttributeOfChildNodeOrNode(n, "LOAD", "schema-file-name");
    }

    public static Node getUnderNode(Node n) {
        return getChildNode(n, "UNDER");
    }

    public static Node getPublishNode(Node n) {
        return getChildNode(n, "PUBLISH");
    }

    public static Node getIncludeNode(Node n) {
        return getChildNode(n, "INCLUDE");
    }

    public static Node getTuplingNode(Node n) {
        return getChildNode(n, "TUPLING");
    }

    public static Node getDebugNode(Node n) {
        return getChildNode(n, "DEBUG");
    }

    public static Node getCeilingsNode(Node n) {
        return getChildNode(n, "CEILINGS");
    }

    public static String getDebugLevel(Node n) {
        return getAttributeOfChildNodeOrNode(n, "DEBUG", "debug-level");
    }

    public static String getCeilingLevel(Node n) {
        return getAttributeOfChildNodeOrNode(n, "CEILING", "ceiling-level");
    }

    //LISTS
    private static List<String> getRelationsList(Node n) {
        return getListElements(n, "RELATIONS", "name");
    }

    private static List<String> getUnderList(Node n) {
        // The under node is the "list" node, so need to be passed the EXPLORE node,
        // not the UNDER node for getListElements to work.
        List<String> result = getListElements(n, "UNDER", "pname");
        writeToLog("UNDER LIST: " + result.toString());
        return result;
    }

    //Returns the child node of n whose name is nodeName
    private static Node getChildNode(Node n, String nodeName) {
        List<Node> childNodes = getChildNodes(n, nodeName);
        if (childNodes.size() > 0)
            return childNodes.get(0);
        return null; // Didn't find it, error
    }

    private static List<Node> getChildNodes(Node n, String nodeName) {
        List<Node> childNodes = getElementChildren(n);
        List<Node> result = new ArrayList<Node>();

        for (Node childNode : childNodes) {
            if (nodeName.equalsIgnoreCase(childNode.getNodeName())) {
                result.add(childNode);
            }
        }
        return result;
    }

    /**
     * Returns the value of attribute [attributeName] in either
     * (1) Node n (if n's name is [nodeName])
     * (2) The first child of Node n named [nodeName])
     * @param n
     * @param nodeName
     * @param attributeName
     * @return
     */
    private static String getAttributeOfChildNodeOrNode(Node n, String nodeName, String attributeName) {
        if (n == null) {
            throw new MCommunicatorException("getAttributeofChildNodeorNode called with n=null.");
        }

        Node targetNode = null;
        if (n.getNodeName().equalsIgnoreCase(nodeName)) {
            targetNode = n;
        } else {
            targetNode = getChildNode(n, nodeName);
            if (targetNode == null)
                throw new MCommunicatorException(
                        "getAttributeofChildNodeorNode: Neither node nor children had name=" + nodeName);
        }

        return getNodeAttribute(targetNode, attributeName);
    }

    private static String getNodeAttribute(Node n, String attributeName) {
        if (n == null) {
            throw new MCommunicatorException("getNodeAttribute called with n=null.");
        }

        Node attribute = n.getAttributes().getNamedItem(attributeName);
        if (attribute == null)
            throw new MCommunicatorException("getNodeAttribute: Node did not have attribute=" + attributeName);
        return attribute.getNodeValue();
    }

    //Returns a list of the attribute values associated with the attributeName of every childNode of a Node named listName, which is itself a child node of n
    private static List<String> getListElements(Node n, String listName, String attributeName) {
        Node listNode = getChildNode(n, listName);
        writeToLog("\nIn getListElements. Node: " + n.getNodeName() + "; listName: " + listName
                + "; attributeName: " + attributeName + "\n");

        //Return null if we can't find the node
        if (listNode == null) {
            writeToLog("\nNo child node found with that name. Returning null.\n");
            return null;
        }

        LinkedList<String> attributeValues = new LinkedList<String>();

        List<Node> childNodes = getElementChildren(listNode);
        for (Node aNode : childNodes) {
            attributeValues.add(aNode.getAttributes().getNamedItem(attributeName).getNodeValue());
        }
        return attributeValues;
    }

    private static List<Node> getElementChildren(Node n) {
        // NodeList does not implement Iterable

        List<Node> result = new ArrayList<Node>();
        if (n == null)
            return result;
        NodeList nlResult = n.getChildNodes();

        if (nlResult == null)
            return result;

        for (int ii = 0; ii < nlResult.getLength(); ii++) {
            Node aChild = nlResult.item(ii);
            //if(aChild.getNodeType() != Node.TEXT_NODE)
            if (aChild.getNodeType() == Node.ELEMENT_NODE)
                result.add(aChild);
        }

        return result;
    }

    //Expects the node one down from condition node
    private static MExploreCondition exploreHelper(Node n, List<String> bound)
            throws MUserException, MGEManagerException, MGEUnknownIdentifier {
        assert (n != null);

        List<Node> childNodes = getElementChildren(n);

        String name = n.getNodeName();

        //writeToLog("\nIn exploreHelper. Node Name: " + name + "\n");

        //if(childNodes.getLength() == 0)
        //   writeToLog("\nNo child nodes.\n");
        //else
        //   writeToLog("First child node's name: " + childNodes.item(0).getNodeName() + "\n");

        if (name.equalsIgnoreCase("AND")) {
            assert (getElementChildren(n).size() == 2);
            return exploreHelper(childNodes.get(0), bound).and(exploreHelper(childNodes.get(1), bound));
        } else if (name.equalsIgnoreCase("OR")) {
            assert (getElementChildren(n).size() == 2);
            return exploreHelper(childNodes.get(0), bound).or(exploreHelper(childNodes.get(1), bound));
        } else if (name.equalsIgnoreCase("IMPLIES")) {
            // cannot trust XML to preserve node order
            Node antecedent = getChildNode(n, "ANTE");
            Node consequent = getChildNode(n, "CONS");

            // The children of these nodes are the formulas in ante/cons position.
            MExploreCondition antecedentFmla = exploreHelper(antecedent.getFirstChild(), bound);
            MExploreCondition consequentFmla = exploreHelper(consequent.getFirstChild(), bound);

            return antecedentFmla.implies(consequentFmla);
        } else if (name.equalsIgnoreCase("ISA")) {
            String typename = getAttributeOfChildNodeOrNode(n, "ISA", "sort");
            Relation theRel = MFormulaManager.makeRelation(typename, 1);

            // These may be null
            Node internalFmlaWrapNode = getChildNode(n, "FORMULA");
            Node internalTermWrapNode = getChildNode(n, "TERM");

            MExploreCondition internalFmlaC;
            MExploreCondition newFmlaC;

            // If no fmla passed, this is a sort-as-predicate. Just checking whether the var is in the sort.
            if (internalFmlaWrapNode == null)
                internalFmlaC = new MExploreCondition(true);
            else
                internalFmlaC = exploreHelper(internalFmlaWrapNode.getFirstChild(), bound);

            MTerm theTerm = termHelper(internalTermWrapNode.getFirstChild(), bound);
            newFmlaC = internalFmlaC.isaSubstitution(theTerm.expr, theRel);

            writeToLog("\nFormula helper (ISA) got " + theTerm + " : " + theRel + " | " + internalFmlaC);
            writeToLog("\n  Substituted to: " + newFmlaC);

            // The term in the ISA _must_ be added to the context, since it isn't necessarily
            // part of the formula condition. (For instance, TRUE isas)
            newFmlaC.termMap.put(theTerm.expr, theTerm);

            return newFmlaC;
        } else if (name.equalsIgnoreCase("EQUALS")) {
            return handleEqualsFormula(n, bound);
        } else if (name.equalsIgnoreCase("IFF")) {
            assert (getElementChildren(n).size() == 2);
            return exploreHelper(n.getFirstChild(), bound).iff(exploreHelper(n.getChildNodes().item(1), bound));
        } else if (name.equalsIgnoreCase("NOT")) {
            return exploreHelper(n.getFirstChild(), bound).not();
        } else if (name.equalsIgnoreCase("EXISTS")) {
            String theVarName = getAttributeOfChildNodeOrNode(n, "EXISTS", "var");
            String theSortName = getAttributeOfChildNodeOrNode(n, "EXISTS", "sort");

            Variable theVar = MFormulaManager.makeVariable(theVarName);
            Relation theSort = MFormulaManager.makeRelation(theSortName, 1);

            List<String> newbound = new ArrayList<String>(bound);
            newbound.add(theVarName);

            return exploreHelper(n.getFirstChild(), newbound).exists(theVar, theSort);
        } else if (name.equalsIgnoreCase("FORALL")) {
            String theVarName = getAttributeOfChildNodeOrNode(n, "FORALL", "var");
            String theSortName = getAttributeOfChildNodeOrNode(n, "FORALL", "sort");

            Variable theVar = MFormulaManager.makeVariable(theVarName);
            Relation theSort = MFormulaManager.makeRelation(theSortName, 1);

            List<String> newbound = new ArrayList<String>(bound);
            newbound.add(theVarName);

            return exploreHelper(n.getFirstChild(), newbound).forall(theVar, theSort);
        } else if (name.equalsIgnoreCase("ATOMIC-FORMULA")) {
            return handleAtomicFormula(n, bound);
        } else if (name.equalsIgnoreCase("TRUE")) {
            return new MExploreCondition(true);
        } else if (name.equalsIgnoreCase("FALSE")) {
            return new MExploreCondition(false);
        }

        throw new MUserException("exploreHelper was unable to match node type: " + name);
    }

    private static MExploreCondition handleEqualsFormula(Node n, List<String> bound) {
        // Comes in with 2 TERMS now instead of 2 maybe-variables.

        List<MTerm> terms = getTermsFromEqualsFmla(n, bound);
        MTerm term1 = terms.get(0);
        MTerm term2 = terms.get(1);

        writeToLog("\nexploreHelper: EQUALS: " + term1 + " = " + term2 + "\n\n");

        Formula fmla = MFormulaManager.makeEqAtom(term1.expr, term2.expr);
        return new MExploreCondition(fmla, term1, term2, true);
    }

    private static List<MTerm> getTermsFromEqualsFmla(Node n, List<String> bound) {
        List<Node> childNodes = getElementChildren(n);

        List<MTerm> terms = new ArrayList<MTerm>(2);

        terms.add(termHelper(childNodes.get(0), bound));
        terms.add(termHelper(childNodes.get(1), bound));
        return terms;
    }

    private static MExploreCondition handleAtomicFormula(Node n, List<String> bound) {

        List<String> relationNameComponents = getRelationNameFromAtomicFmla(n);
        List<MTerm> terms = getTermsFromAtomicFmla(n, bound);

        /////////////////////////////////////////////////////

        // Could be:
        // (1) compound relation name, e.g. pol.idbname
        // (2) EDB name
        // (3) view name (really an idb!)

        writeToLog("\nAtomic-Formula: \nrelationNameComponents: " + relationNameComponents + "\nterms: "
                + terms.toString());

        if (relationNameComponents.size() < 1)
            throw new MUserException("Unable to obtain relation name in atomic formula.");

        String relationName = relationNameComponents.get(relationNameComponents.size() - 1);

        if (relationNameComponents.size() == 1) {
            MIDBCollection pol = MEnvironment.getPolicyOrView(relationName);

            writeToLog("\n      relationName: " + relationName + "\npol: " + pol + "\n\n");

            /////////////////////////////////////////////////////
            // (3) view?
            if (pol != null) {
                Formula idbf = MEnvironment.getOnlyIDB(relationName);
                if (idbf != null) {
                    // Perform variable substitution
                    writeToLog("\nView IDB before substitution: " + idbf);
                    idbf = performSubstitution(relationName, pol, idbf, terms);

                    // Assemble MExploreCondition object
                    writeToLog("\nNew Explore condition (view): " + idbf);
                    return new MExploreCondition(idbf, pol, relationName, terms);
                }
            }

            /////////////////////////////////////////////////////
            // (2) EDB, then!

            // We don't have a vocabulary yet. So just make the relation.
            // The manager will prevent duplicates.
            Relation rel = MFormulaManager.makeRelation(relationName, terms.size());

            Expression termvector;
            Formula f = null;

            termvector = MFormulaManager.makeTermTuple(terms);
            f = MFormulaManager.makeAtom(termvector, rel);

            // No variable substitution needed!
            writeToLog("\nNew Explore condition (EDB): " + f);
            return new MExploreCondition(f, rel, terms);

        }

        /////////////////////////////////////////////////////
        // (1) compound relation name (reference to a policy, etc.)
        String collName = "";
        for (int ii = 0; ii < relationNameComponents.size() - 1; ii++) {
            collName += relationNameComponents.get(ii);
        }

        MIDBCollection pol = MEnvironment.getPolicyOrView(collName);
        if (pol == null)
            throw new MUserException("Unknown policy: " + collName);

        // throws exception rather than returning null
        Formula idbf = validateDBIdentifier(collName, relationName);

        // Substitute variables in policy's IDB for terms in query
        writeToLog("\nNon-view IDB before substitution: " + idbf);
        idbf = performSubstitution(relationName, pol, idbf, terms);

        MCommunicator.writeToLog("\nSAW TERMS: " + terms);

        writeToLog("\nNew Explore condition (non-view IDB): " + idbf);
        return new MExploreCondition(idbf, pol, relationName, terms);
    }

    private static List<MTerm> getTermsFromNode(Node termsNode, List<String> bound) {
        List<Node> termsNodeComponents = getElementChildren(termsNode);
        List<MTerm> terms = new ArrayList<MTerm>();
        for (Node theNode : termsNodeComponents) {
            MTerm theTerm = termHelper(theNode, bound);
            //String nameStr = getNodeAttribute(theNode, "ID", "id");
            terms.add(theTerm);
        }
        return terms;
    }

    private static List<MTerm> getTermsFromAtomicFmla(Node n, List<String> bound) {
        /////////////////////////////////////////////////////
        // Terms
        Node termsNode = getChildNode(n, "TERMS");
        return getTermsFromNode(termsNode, bound);
    }

    private static List<String> getRelationNameFromAtomicFmla(Node n) {
        /////////////////////////////////////////////////////
        // Relation name
        Node relationNameNode = getChildNode(n, "RELATION-NAME");
        List<Node> relationComponents = getElementChildren(relationNameNode);
        List<String> relationNameComponents = new ArrayList<String>();
        for (Node theNode : relationComponents) {
            // <ID id=\"P\"/>
            String nameStr = getAttributeOfChildNodeOrNode(theNode, "ID", "id");
            relationNameComponents.add(nameStr);
        }
        return relationNameComponents;
    }

    private static MTerm termHelper(Node n, List<String> bound) {
        List<Node> childNodes = getElementChildren(n);

        String name = n.getNodeName();

        //writeToLog("\nIn exploreHelper. Node Name: " + name + "\n");

        //if(childNodes.getLength() == 0)
        //   writeToLog("\nNo child nodes.\n");
        //else
        //   writeToLog("First child node's name: " + childNodes.item(0).getNodeName() + "\n");

        if (name.equalsIgnoreCase("FUNCTION-TERM")) {
            String funcName = getAttributeOfChildNodeOrNode(n, "FUNCTION-TERM", "func");

            List<MTerm> subTerms = new ArrayList<MTerm>();
            for (Node aNode : childNodes) {
                MTerm aChildTerm = termHelper(aNode, bound);
                subTerms.add(aChildTerm);
            }

            return new MFunctionTerm(funcName, subTerms);
        } else if (name.equalsIgnoreCase("CONSTANT-TERM")) {
            String constName = getAttributeOfChildNodeOrNode(n, "CONSTANT-TERM", "id");
            return new MConstantTerm(constName);
        } else if (name.equalsIgnoreCase("VARIABLE-TERM")) {
            String varName = getAttributeOfChildNodeOrNode(n, "VARIABLE-TERM", "id");

            // Removed this check on June 9 2013 - TN
            // After talking with SK, we want a SQL style "project" for the declared vector.
            // Thus, if a variable is not declared, we simply need to implicitly bind it via exists
            // at the top of the formula

            // If unbound, error! (null means: don't do the check)
            //if(bound != null && !bound.contains(varName))
            //{
            //   throw new MUserException("Variable named "+varName+" occured in the query formula, but was not bound in the query's variable list or by a quantifier.");
            //}
            return new MVariableTerm(varName);
        } else {
            throw new MCommunicatorException("Unsupported term type: " + name);
        }
    }

    //Returns a list containing the value of the first attribute of every child node of n
    protected static List<String> getListChildren(Node n) {
        List<Node> childNodes = getElementChildren(n);
        List<String> list = new LinkedList<String>();

        for (Node aNode : childNodes) {
            list.add(aNode.getAttributes().item(0).getNodeValue());
        }

        return list;
    }

    static void initializeLog() {
        if (!bDoLogging)
            return;

        // Wipe the log clean every time the engine runs.
        try {
            // This only works if the .class file isn't in a .jar; it's also risky because the place Margrave is
            // installed may not be writable.

            //URL absoluteClassPathNameURL = MCommunicator.class.getClass().getResource("/edu/wpi/margrave/MCommunicator.class");
            //URL absoluteLogFileNameURL = new URL(absoluteClassPathNameURL, sLogFileName);
            //File logFILE = new File(absoluteLogFileNameURL.getFile());
            //String absoluteLogFileName = java.net.URLDecoder.decode(logFILE.toString(), "UTF-8");

            // Instead, default to the system's temp file folder:
            String tempFolder = System.getProperty("java.io.tmpdir");
            if (!tempFolder.endsWith(File.separator))
                tempFolder += File.separator;
            String absoluteLogFileName = tempFolder + sLogFileName;

            MCommunicator.outLogStream = new FileWriter(absoluteLogFileName);
            MCommunicator.outLog = new BufferedWriter(outLogStream);
            MCommunicator.outLog.write("Margrave engine log started at: " + new Date() + "\n");
            MCommunicator.outLog.write("======================================================================\n");
            MCommunicator.outLog.flush();

            // Log is initialized and open
        } catch (IOException e) {
            // Couldn't initialize log. try to report back to Racket.
            out.println(makeDetailedError("\nError initializing log file: " + e.getMessage() + " (exception: " + e
                    + ")" + "( outLog = " + outLog + ")" + "( outLogStream = " + outLogStream + ")"));
            out.flush();
            System.exit(3);
        }

    }

    static void writeToLog(String s) {
        if (!bDoLogging)
            return;
        try {
            MCommunicator.outLog.write(s);
            MCommunicator.outLog.flush();
        } catch (Exception e) {
            out.println(makeDetailedError("\nError writing log file: " + e.getMessage() + " (exception: " + e + ")"
                    + "( outLog = " + outLog + ")" + "( outLogStream = " + outLogStream + ")"));
            out.flush();
            System.exit(3);
        }
    }

    protected static String transformXMLToString(Document theResponse) {
        try {
            Transformer transformer = TransformerFactory.newInstance().newTransformer();
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");

            //initialize StreamResult with File object to save to file
            StreamResult result = new StreamResult(new StringWriter());
            DOMSource source = new DOMSource(theResponse);

            // If this line causes a null pointer exception, there is an empty text element somewhere.
            // For some reason the transformer can't handle text elements with "" in them.
            transformer.transform(source, result);

            String xmlString = result.getWriter().toString();
            return xmlString;
        } catch (Exception e) {
            // Will hit this if theResponse is null.
            // don't do this. would go through System.err
            //e.printStackTrace();
            return (makeDetailedError(e.getLocalizedMessage()));
        }

    }

    protected static byte[] transformXMLToByteArray(Document theResponse) {
        return transformXMLToString(theResponse).getBytes();
    }

    protected static Formula performSubstitution(String collectionIdSymbol, MIDBCollection coll, Formula f,
            List<MTerm> newterms) throws MUserException, MGEUnknownIdentifier {
        // Replace expressions (here, variables) with other expressions
        // e.g. x becomes f(y, c)

        List<Expression> newtermsexprs = new ArrayList<Expression>(newterms.size());
        for (MTerm t : newterms)
            newtermsexprs.add(t.expr);

        return MEnvironment.performSubstitution(collectionIdSymbol, coll, f, newtermsexprs);
    }

    protected static Formula validateDBIdentifier(String objn, String dbn) throws MUserException {

        writeToLog("\nMCommunicator.validateDBIdentifier invoked for: " + objn + ", " + dbn);

        // Is objn a policy name? If not, error.
        MIDBCollection pol = MEnvironment.getPolicyOrView(objn);

        if (pol == null)
            throw new MGEUnknownIdentifier("Unknown IDB Collection: " + objn);

        writeToLog(
                "\n  Collection found. Self reported name=" + pol.name + " ContainsIDB = " + pol.containsIDB(dbn));

        if (pol.containsIDB(dbn)) {
            writeToLog("\n  Returning: " + pol.getIDB(dbn));
            return pol.getIDB(dbn);
        } else {
            writeToLog("Unknown IDB: " + dbn + " in collection: " + objn + ". IDBs were: " + pol.idbKeys());
            throw new MGEUnknownIdentifier("Unknown IDB: " + dbn + " in collection: " + objn);
        }
    }

    private static List<MIDBCollection> namesToIDBCollections(List<String> names) throws MUserException {
        if (names == null)
            return new ArrayList<MIDBCollection>();

        List<MIDBCollection> result = new ArrayList<MIDBCollection>(names.size());

        for (String n : names) {
            if (MEnvironment.getPolicyOrView(n) == null)
                throw new MGEUnknownIdentifier("Unknown symbol in UNDER clause: " + n);
            result.add(MEnvironment.getPolicyOrView(n));
        }

        return result;
    }

    public static void unitTests() {
        MEnvironment.writeErrLine("----- Begin MCommunicator Tests (No messages is good.) -----");

        // Test XML handling.
        // handleXMLCommand

        //   String testInfo = "<MARGRAVE-COMMAND type=\"INFO\"><INFO /></MARGRAVE-COMMAND> ";
        //   String testInfoWithID = "<MARGRAVE-COMMAND type=\"INFO\"><INFO id=\"Something\" /></MARGRAVE-COMMAND>  ";
        //   String reset = "<MARGRAVE-COMMAND type=\"RESET\"><ID>MyQuery</ID></MARGRAVE-COMMAND>";  // ***
        //   String show = "<MARGRAVE-COMMAND type=\"SHOW\"><SHOW type=\"ONE\" ID=\"MyQuery\" /></MARGRAVE-COMMAND> ";
        //   String count = "<MARGRAVE-COMMAND type=\"COUNT\"><COUNT ID=\"MyQuery\" /></MARGRAVE-COMMAND> ";
        //   String isposs = "<MARGRAVE-COMMAND type=\"IS-POSSIBLE\"><IS-POSSIBLE ID=\"MyQuery\" /></MARGRAVE-COMMAND> ";
        /*   String showUnrealizedForCases =
        "<MARGRAVE-COMMAND type=\"SHOW\"><SHOW type=\"UNREALIZED\" ID=\"Myquery\">"+
        "<ATOMIC-FORMULA><RELATION-NAME><ID id=\"P\" /><ID id=\"R\" /></RELATION-NAME><TERMS><VARIABLE-TERM id=\"x\" /><VARIABLE-TERM id=\"y\" /><FUNCTION-TERM func=\"f\"><CONSTANT-TERM id=\"c\" /><VARIABLE-TERM id=\"z\" /></FUNCTION-TERM></TERMS></ATOMIC-FORMULA>"+
        "<ATOMIC-FORMULA><RELATION-NAME><ID id=\"P\" /><ID id=\"R2\" /></RELATION-NAME><TERMS><VARIABLE-TERM id=\"z\" /><CONSTANT-TERM id=\"c\" /></TERMS></ATOMIC-FORMULA>" +
        "<FORCASES><ATOMIC-FORMULA><RELATION-NAME><ID id=\"IDB\" /></RELATION-NAME><TERMS><VARIABLE-TERM id=\"x\" /></TERMS></ATOMIC-FORMULA>" +
        "<ATOMIC-FORMULA><RELATION-NAME><ID id=\"P\" /><ID id=\"R3\" /></RELATION-NAME><TERMS><VARIABLE-TERM id=\"y\" /></TERMS></ATOMIC-FORMULA></FORCASES></SHOW></MARGRAVE-COMMAND> ";
         */

        String aQuery = "<MARGRAVE-COMMAND type=\"EXPLORE\"><EXPLORE id=\"Myqry\"><CONDITION><OR>"
                + "<ATOMIC-FORMULA><RELATION-NAME><ID id=\"P\"/><ID id=\"permit\"/></RELATION-NAME><TERMS><CONSTANT-TERM id=\"c\" /><FUNCTION-TERM func=\"f\"><CONSTANT-TERM id=\"c\" /></FUNCTION-TERM></TERMS></ATOMIC-FORMULA>"
                + "<AND><EQUALS><VARIABLE-TERM id=\"x\" /><VARIABLE-TERM id=\"y\" /></EQUALS>"
                + "<OR> <ISA sort=\"U\"><TERM><VARIABLE-TERM id=\"x\" /></TERM></ISA> <ISA sort=\"U\"><TERM><VARIABLE-TERM id=\"x\" /></TERM></ISA> "
                + "<EQUALS><VARIABLE-TERM id=\"x\" /><VARIABLE-TERM id=\"y\" /></EQUALS></OR>"
                + "</AND></OR></CONDITION>"
                + "<PUBLISH><VARIABLE-DECLARATION sort=\"B\" varname=\"y\" /><VARIABLE-DECLARATION sort=\"C\" varname=\"x\" /></PUBLISH></EXPLORE></MARGRAVE-COMMAND> ";

        // aQuery unsat since publish gives us types of x, y that don't fit the query.

        String aQuery2 = "<MARGRAVE-COMMAND type=\"EXPLORE\"><EXPLORE id=\"Myqry2\"><CONDITION>"
                + "<AND><NOT><ATOMIC-FORMULA><RELATION-NAME><ID id=\"P\"/><ID id=\"permit\"/></RELATION-NAME><TERMS><VARIABLE-TERM id=\"x\" /><VARIABLE-TERM id=\"y\" /></TERMS></ATOMIC-FORMULA></NOT>"
                + "<EQUALS><VARIABLE-TERM id=\"x\" /><VARIABLE-TERM id=\"y\" /></EQUALS>"
                + "<EQUALS><VARIABLE-TERM id=\"x\" /><FUNCTION-TERM func=\"f\"><CONSTANT-TERM id=\"c\" /></FUNCTION-TERM></EQUALS></AND></CONDITION>"
                + "<PUBLISH><VARIABLE-DECLARATION sort=\"U\" varname=\"y\" /><VARIABLE-DECLARATION sort=\"U\" varname=\"x\" /></PUBLISH></EXPLORE></MARGRAVE-COMMAND> ";

        // aQuery2 sat

        List<String> creationCommands = new ArrayList<String>();

        creationCommands.add(
                "<MARGRAVE-COMMAND type=\"ADD\"><VOCAB-IDENTIFIER vname=\"Test1\" /><SORT-WITH-CHILDREN name=\"U\"><SORT name=\"A\" /><SORT name=\"B\" /><SORT name=\"C\" /></SORT-WITH-CHILDREN></MARGRAVE-COMMAND> ");
        creationCommands.add(
                "<MARGRAVE-COMMAND type=\"ADD\"><VOCAB-IDENTIFIER vname=\"Test1\" /><PREDICATE name=\"r\" /><RELATIONS><RELATION name=\"A\"/><RELATION name=\"B\"/><RELATION name=\"C\"/><RELATION name=\"C\"/></RELATIONS></MARGRAVE-COMMAND> ");
        creationCommands.add(
                "<MARGRAVE-COMMAND type=\"ADD\"><VOCAB-IDENTIFIER vname=\"Test1\" /><CONSTANT name=\"c\" type=\"C\" /></MARGRAVE-COMMAND>");
        creationCommands.add(
                "<MARGRAVE-COMMAND type=\"ADD\"><VOCAB-IDENTIFIER vname=\"Test1\" /><FUNCTION name=\"f\"><RELATIONS><RELATION name=\"C\" /><RELATION name=\"A\" /></RELATIONS></FUNCTION></MARGRAVE-COMMAND> ");

        creationCommands.add(
                "<MARGRAVE-COMMAND type=\"CREATE POLICY LEAF\"><POLICY-IDENTIFIER pname=\"P\" /><VOCAB-IDENTIFIER vname=\"Test1\" /></MARGRAVE-COMMAND> ");
        creationCommands.add(
                "<MARGRAVE-COMMAND type=\"ADD\"><POLICY-IDENTIFIER pname=\"P\" /><VARIABLE-DECLARATION sort=\"A\" varname=\"x\" /></MARGRAVE-COMMAND>");
        creationCommands.add(
                "<MARGRAVE-COMMAND type=\"ADD\"><POLICY-IDENTIFIER pname=\"P\" /><VARIABLE-DECLARATION sort=\"A\" varname=\"y\" /></MARGRAVE-COMMAND>");

        creationCommands.add(
                "<MARGRAVE-COMMAND type=\"ADD\"><POLICY-IDENTIFIER pname=\"P\" /><RULE name=\"Rule1\"><DECISION-TYPE type=\"permit\"><ID id=\"x\" /><ID id=\"y\" /></DECISION-TYPE>"
                        + "<TARGET><AND><ATOMIC-FORMULA><RELATION-NAME><ID id=\"r\" /></RELATION-NAME><TERMS><VARIABLE-TERM id=\"x\" /><VARIABLE-TERM id=\"x\" /><VARIABLE-TERM id=\"x\" /><VARIABLE-TERM id=\"x\" /></TERMS></ATOMIC-FORMULA>"
                        + "<ISA sort=\"B\" > <TERM><VARIABLE-TERM id=\"x\" /></TERM></ISA>"
                        + "</AND></TARGET></RULE></MARGRAVE-COMMAND>");

        // FA {Permit, Deny}, but CallPolice overrides both.
        creationCommands.add(
                "<MARGRAVE-COMMAND type=\"SET RCOMBINE FOR POLICY\"><POLICY-IDENTIFIER pname=\"P\" /><COMB-LIST>"
                        + "<FA><ID id=\"permit\" /><ID id=\"deny\" /></FA>"
                        + "<OVERRIDES decision=\"permit\"><ID id=\"callpolice\" /></OVERRIDES>"
                        + "<OVERRIDES decision=\"deny\"><ID id=\"callpolice\" /></OVERRIDES></COMB-LIST></MARGRAVE-COMMAND>");
        creationCommands
                .add("<MARGRAVE-COMMAND type=\"PREPARE\"><POLICY-IDENTIFIER pname=\"P\" /></MARGRAVE-COMMAND>");

        //creationCommands.add("");

        for (String cmd : creationCommands) {
            handleXMLCommand(cmd);
        }

        String aShow = "<MARGRAVE-COMMAND type=\"SHOW\"><SHOW type=\"NEXT\" id=\"Myqry\" /></MARGRAVE-COMMAND>";
        String aShow2 = "<MARGRAVE-COMMAND type=\"SHOW\"><SHOW type=\"NEXT\" id=\"Myqry2\" /></MARGRAVE-COMMAND>";
        String aReset = "<MARGRAVE-COMMAND type=\"RESET\"><RESET id=\"Myqry\" /></MARGRAVE-COMMAND>";

        handleXMLCommand(aQuery);
        handleXMLCommand(aShow); // results in a model xml response (or unsat)
        handleXMLCommand(aShow); // test MULTIPLE unsat in a row (don't get iterator exception)
        handleXMLCommand(aReset); // success

        handleXMLCommand(aQuery2);
        handleXMLCommand(aShow2); // results in a model xml response (or unsat)
        handleXMLCommand(aShow2); // results in a model xml response (or unsat)
        handleXMLCommand(aShow2); // results in a model xml response (or unsat)
        handleXMLCommand(aShow2); // results in a model xml response (or unsat)

        MEnvironment.debug();

        MEnvironment.writeErrLine("----- End MCommunicator Tests -----");
    }

}