MarrsProject() {
   setColumns(MarrsColumn.TITLE);
   parameters.addTableModelListener(this);
 }
  public void saveToFile(File dest) throws IOException {
    File temp = File.createTempFile(dest.getName(), ".tmp", dest.getParentFile());

    try {
      Element root = new Element("MarrsProject");
      root.setAttribute("title", title);
      root.setAttribute("schemaversion", "" + CURRENT_SCHEMAVERSION);

      for (MarrsQuery q : rows) {
        Element queryElt = new Element("Query");
        CDATA data = new CDATA(q.getQueryText());
        queryElt.addContent(data);

        queryElt.setAttribute("title", q.getTitle());
        queryElt.setAttribute("type", "" + q.getQueryType());

        if (q.getAskBefore() != null) {
          Element askElt = new Element("AskBefore");
          askElt.setAttribute("key", q.getAskBefore());
          queryElt.addContent(askElt);
        }

        for (Map.Entry<String, String> context : q.getContext().entrySet()) {
          Element contextElt = new Element("Context");
          contextElt.setAttribute(
              "key", context.getKey()); // for now, we only use "type" to filter context, so this is
          // hardcoded.
          contextElt.setAttribute("value", context.getValue());
          queryElt.addContent(contextElt);
        }

        for (String var : q.getPostProcessingVars()) {
          Element processElt = new Element("PostProcessing");
          processElt.setAttribute(
              "var", var); // for now, we only use "type" to filter context, so this is hardcoded.
          processElt.setAttribute("operation", q.getPostProcessingOperation(var));
          queryElt.addContent(processElt);
        }

        root.addContent(queryElt);
      }

      for (Map.Entry<String, String> param : parameters.entrySet()) {
        Element paramElt = new Element("Param");
        paramElt.setAttribute("key", param.getKey());
        paramElt.setAttribute("val", param.getValue());
        root.addContent(paramElt);
      }

      for (NodeAttribute attr : nodeAttributes) {
        Element nodeAttr = new Element("NodeAttribute");
        nodeAttr.setAttribute("key", attr.key);
        nodeAttr.setAttribute("value", attr.value);

        for (Map.Entry<String, String> e : attr.vizprops.entrySet()) {
          Element vizprop = new Element("Vizmap");
          vizprop.setAttribute("prop", e.getKey());
          vizprop.setAttribute("value", e.getValue());
          nodeAttr.addContent(vizprop);
        }
        root.addContent(nodeAttr);
      }

      Document doc = new Document(root);
      XMLOutputter xout = new XMLOutputter();
      Format format = Format.getPrettyFormat();
      format.setIndent("\t");
      format.setLineSeparator("\n");
      format.setTextMode(TextMode.PRESERVE);
      xout.setFormat(format);
      xout.output(doc, new FileOutputStream(temp));

      temp.renameTo(dest);
      dirty = false;
    } finally {
      temp.delete();
    }
  }
  public String getSubstitutedQuery(MarrsQuery mq) throws MarrsException {
    String q = mq.getQueryText();
    Map<String, String> queryData = getQueryParameters(mq);

    for (String key : queryData.keySet()) {

      if (!parameters.containsKey(key)) {
        throw new MarrsException("Missing substitution parameter: " + key);
      }

      String contents = parameters.get(key);
      String filter = queryData.get(key);
      if ("uri-list".equals(filter)) {
        Pattern pat =
            Pattern.compile("\\s*<[-~#_:/%.0-9a-zA-Z]+>(\\s*,\\s*<[-~#_:/%.0-9a-zA-Z]+>)*\\s*$");
        Matcher mat = pat.matcher(contents);
        if (!mat.matches()) {
          throw new MarrsException(
              "'"
                  + contents
                  + "' does not match pattern for parameter ${"
                  + key
                  + "|"
                  + filter
                  + "}");
        }
      } else if ("uri-bracketed"
          .equals(
              filter)) /* difference between uri and uri-bracketed is the surrounding < and > characters */ {
        Pattern pat = Pattern.compile("\\s*<[-~#_:/%.0-9a-zA-Z]+>\\s*");
        Matcher mat = pat.matcher(contents);
        if (!mat.matches()) {
          throw new MarrsException(
              "'"
                  + contents
                  + "' does not match pattern for parameter ${"
                  + key
                  + "|"
                  + filter
                  + "}");
        }
      } else if ("uri".equals(filter)) {
        Pattern pat = Pattern.compile("[-~#_:/%.0-9a-zA-Z]+");
        Matcher mat = pat.matcher(contents);
        if (!mat.matches()) {
          throw new MarrsException(
              "'"
                  + contents
                  + "' does not match pattern for parameter ${"
                  + key
                  + "|"
                  + filter
                  + "}");
        }
      } else if ("literal".equals(filter)) {
        Pattern pat = Pattern.compile("^[^\"\\n]*$");
        Matcher mat = pat.matcher(contents);
        if (!mat.matches()) {
          throw new MarrsException(
              "'"
                  + contents
                  + "' does not match pattern for parameter ${"
                  + key
                  + "|"
                  + filter
                  + "}");
        }
      } else {
        /* most restrictive base pattern */
        Pattern pat = Pattern.compile("[-~#_:/%.0-9a-zA-Z]+");
        Matcher mat = pat.matcher(contents);
        if (!mat.matches()) {
          throw new MarrsException(
              "'" + contents + "' does not match pattern for parameter ${" + key + "}");
        }
      }

      // TODO: replace & appendReplace
      q = q.replaceAll("\\$\\{" + key + "(\\|([\\w-]+))?\\}", contents);
    }
    System.out.println(q);
    return q;
  }
 public String getQueryParameter(String key) {
   return parameters.get(key);
 }
 public void setQueryParameter(String key, String value) {
   parameters.put(key, value);
 }