/**
   * Determines the number of bytes that will be used to store the serialized representation of a
   * single SearchResponse (assuming UTF-8 encoding)
   *
   * @param response The SearchResponse to serialize and evaluate
   * @return The number of bytes used to store the serialized response
   */
  public static int getSerializedResponseBytes(SearchResponse response) throws IOException {
    StringWriter serializeBuffer = new StringWriter();
    Node serializeNode = serializeSearchResponse(response);
    TransformerHelper.encodedTransform((Element) serializeNode, "UTF-8", serializeBuffer, true);

    return serializeBuffer.toString().length() * 2; // 2 bytes per character in UTF-8
  }
  /*
   * @see up2p.peer.generic.message.SerializableMessage#serialize()
   */
  public Node serialize() {
    // create the XML DOM
    Document document = TransformerHelper.newDocument();

    // create root node
    Element root = document.createElement(GenericPeerMessage.X_UP2P_MESSAGE);
    document.appendChild(root);

    Element current = document.createElement(X_SEARCH_RESPONSE);
    current.setAttribute(X_URL_PREFIX, urlPrefix);
    current.setAttribute(X_SEARCH_ID, id);
    current.setAttribute(X_RESULT_SIZE, String.valueOf(getResultSetSize()));
    current.setAttribute(X_COMMUNITY, communityId);
    root.appendChild(current);

    // Add the metric list to the root node (if applicable)
    if (hostedResIdList != null && !hostedResIdList.isEmpty()) {
      // Generate the metricList node
      Element metricNode = document.createElement(X_HOSTED_RES_ID_LIST);
      // Add all the resource Ids
      for (String rId : hostedResIdList) {
        Element rIdNode = document.createElement(X_RESOURCE_ID);
        rIdNode.setTextContent(rId);
        metricNode.appendChild(rIdNode);
      }
      // Append the metricList to the search result node
      current.appendChild(metricNode);
    }

    // Add any raw trust metrics to the generated XML (if the trust metric map has elements)
    if (!trustMetrics.isEmpty()) {
      Iterator<String> metricKeys = trustMetrics.keySet().iterator();
      while (metricKeys.hasNext()) {
        Element rawMetricNode = document.createElement(X_TRUST_METRIC);
        String metricName = metricKeys.next();
        rawMetricNode.setAttribute(X_ATTR_METRIC_NAME, metricName);
        rawMetricNode.setAttribute(X_ATTR_METRIC_VALUE, trustMetrics.get(metricName));
        current.appendChild(rawMetricNode);
      }
    }

    // create searchResults nodes for each search result
    SearchResponse[] responses = getResponses();
    for (int i = 0; i < responses.length; i++) {
      SearchResponse response = responses[i];
      current.appendChild(document.importNode(serializeSearchResponse(response), true));
    }
    return document.getDocumentElement();
  }
  /**
   * Generates an XML Element representing a single SearchResponse in the format expected by a
   * SearchResponseMessage.
   *
   * @param response The SearchResponse to generate XML for
   * @return The generated XML element
   */
  public static Element serializeSearchResponse(SearchResponse response) {
    Document document = TransformerHelper.newDocument();

    Element searchResult = document.createElement(X_SEARCH_RESULT);
    searchResult.setAttribute(X_TITLE, response.getTitle());
    searchResult.setAttribute(X_RESOURCE_ID, response.getId());
    searchResult.setAttribute(X_FILENAME, response.getFileName());

    // Add article DOM as a child of the searchResult
    try {
      NodeList copyNodes = response.getResourceDOM().getChildNodes();
      for (int k = 0; k < copyNodes.getLength(); k++) {
        Node importedNode = document.importNode(copyNodes.item(k), true);
        searchResult.appendChild(importedNode);
      }

    } catch (DOMException e) {
      e.printStackTrace(); // just output the trace and continue
    }

    return searchResult;
  }
  /** @return The number of bytes the serialized message will require to store in UTF-8 encoding. */
  public int getSerializedBytes() throws IOException {
    StringWriter serializeBuffer = new StringWriter();
    TransformerHelper.encodedTransform((Element) this.serialize(), "UTF-8", serializeBuffer, true);

    return serializeBuffer.toString().length() * 2; // 2 bytes per character in UTF-8
  }
  /**
   * Parses a search response from the given XML fragment.
   *
   * @param xmlNode the XML containing the search response
   * @return the search response
   * @throws MalformedPeerMessageException on a badly formed or invalid message
   */
  public static SearchResponseMessage parse(Node xmlNode) throws MalformedPeerMessageException {

    // check first node name
    try {
      if (xmlNode.getNodeName().equals(GenericPeerMessage.X_UP2P_MESSAGE)) {
        Node currentNode = xmlNode.getFirstChild();
        while (currentNode != null && currentNode.getNodeType() != Node.ELEMENT_NODE)
          currentNode = currentNode.getNextSibling();
        Element current = (Element) currentNode;

        String communityId = current.getAttribute(X_COMMUNITY);
        String searchId = current.getAttribute(X_SEARCH_ID);
        String urlPrefix = current.getAttribute(X_URL_PREFIX);

        SearchResponseMessage response =
            new SearchResponseMessage(searchId, communityId, urlPrefix);

        // add result for each searchResult child, and process the metric list
        // if it exists
        LocationEntry[] entries = null;
        NodeList resultList = current.getChildNodes();
        for (int i = 0; i < resultList.getLength(); i++) {

          // check node type to avoid mixed content
          if (resultList.item(i).getNodeType() == Node.ELEMENT_NODE
              && resultList.item(i).getNodeName().equals(X_SEARCH_RESULT)) {

            // A Search Result element has been found
            String resourceTitle = null;
            String resourceId = null;
            String fileName = null;

            Element result = (Element) resultList.item(i);
            resourceTitle = result.getAttribute(X_TITLE);
            resourceId = result.getAttribute(X_RESOURCE_ID);
            fileName = result.getAttribute(X_FILENAME);

            // Construct the SearchResponse with what we have so far (missing DOM, locations are not
            // serialized)
            SearchResponse newresponse =
                new SearchResponse(
                    resourceId, resourceTitle, communityId, fileName, null, false, searchId);

            if (result.hasChildNodes()) { // in this case, yes! we have a winner!
              // some gymnastics to create the exact same class of Document, in order to be able to
              // adopt nodes...
              // DOMImplementation impl = locationNode.getOwnerDocument().getImplementation();
              Document metadata =
                  TransformerHelper
                      .newDocument(); // impl.createDocument(null,
                                      // "qname",locationNode.getOwnerDocument().getDoctype() );

              /*
              System.out.println("adopter class:"+ metadata.getClass());
              System.out.println("adoptee class:" + locationNode.getOwnerDocument().getClass());
              */

              // Import the metadata into the new document context
              NodeList copyNodes = result.getChildNodes();
              for (int k = 0; k < copyNodes.getLength(); k++) {
                Node importedNode = metadata.importNode(copyNodes.item(k), true);
                metadata.appendChild(importedNode);
              }

              // add the DOM to the searchResponse under construction
              newresponse.addResourceDOM(metadata);
            }

            response.addResult(newresponse);
          } else if (resultList.item(i).getNodeType() == Node.ELEMENT_NODE
              && resultList.item(i).getNodeName().equals(X_HOSTED_RES_ID_LIST)) {

            // A hosted resource id list element has been found

            // The node is a metric list, build the metric list for this search response
            List<String> respMetrics = new ArrayList<String>();
            NodeList metricIds = resultList.item(i).getChildNodes();
            for (int j = 0; j < metricIds.getLength(); j++) {
              respMetrics.add(metricIds.item(j).getTextContent());
            }
            response.setHostedResIdList(respMetrics);
          } else if (resultList.item(i).getNodeType() == Node.ELEMENT_NODE
              && resultList.item(i).getNodeName().equals(X_TRUST_METRIC)) {

            // A trust metric element has been found
            String metricName = ((Element) resultList.item(i)).getAttribute(X_ATTR_METRIC_NAME);
            String metricValue = ((Element) resultList.item(i)).getAttribute(X_ATTR_METRIC_VALUE);
            response.addTrustMetric(metricName, metricValue);
          }
        }
        return response;
      }

      throw new MalformedPeerMessageException(
          ERROR_MSG + " Root node name: " + xmlNode.getNodeName());

    } catch (Exception e) {
      e.printStackTrace();
      throw new MalformedPeerMessageException(ERROR_MSG);
    }
  }