/**
   * Helper method that creates an array of bytes for the specified <tt>Set</tt> of <tt>URN</tt>
   * instances. The bytes are written as specified in HUGE v 0.94.
   *
   * @param urns the <tt>Set</tt> of <tt>URN</tt> instances to use in constructing the byte array
   */
  private static byte[] createExtBytes(Set urns, GGEPContainer ggep) {
    try {
      if (isEmpty(urns) && ggep.isEmpty()) return DataUtils.EMPTY_BYTE_ARRAY;

      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      if (!isEmpty(urns)) {
        // Add the extension for URNs, if any.
        Iterator iter = urns.iterator();
        while (iter.hasNext()) {
          URN urn = (URN) iter.next();
          Assert.that(urn != null, "Null URN");
          baos.write(urn.toString().getBytes());
          // If there's another URN, add the separator.
          if (iter.hasNext()) {
            baos.write(EXT_SEPARATOR);
          }
        }

        // If there's ggep data, write the separator.
        if (!ggep.isEmpty()) baos.write(EXT_SEPARATOR);
      }

      // It is imperitive that GGEP is added LAST.
      // That is because GGEP can contain 0x1c (EXT_SEPARATOR)
      // within it, which would cause parsing problems
      // otherwise.
      if (!ggep.isEmpty()) GGEPUtil.addGGEP(baos, ggep);

      return baos.toByteArray();
    } catch (IOException impossible) {
      ErrorService.error(impossible);
      return DataUtils.EMPTY_BYTE_ARRAY;
    }
  }
  /**
   * Factory method for instantiating individual responses from an <tt>InputStream</tt> instance.
   *
   * @param is the <tt>InputStream</tt> to read from
   * @throws <tt>IOException</tt> if there are any problems reading from or writing to the stream
   */
  public static Response createFromStream(InputStream is) throws IOException {
    // extract file index & size
    long index = ByteOrder.uint2long(ByteOrder.leb2int(is));
    long size = ByteOrder.uint2long(ByteOrder.leb2int(is));

    if ((index & 0xFFFFFFFF00000000L) != 0) throw new IOException("invalid index: " + index);
    // must use Integer.MAX_VALUE instead of mask because
    // this value is later converted to an int, so we want
    // to ensure that when it's converted it doesn't become
    // negative.
    if (size > Integer.MAX_VALUE || size < 0) throw new IOException("invalid size: " + size);

    // The file name is terminated by a null terminator.
    // A second null indicates the end of this response.
    // Gnotella & others insert meta-information between
    // these null characters.  So we have to handle this.
    // See http://gnutelladev.wego.com/go/
    //         wego.discussion.message?groupId=139406&
    //         view=message&curMsgId=319258&discId=140845&
    //         index=-1&action=view

    // Extract the filename
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    int c;
    while ((c = is.read()) != 0) {
      if (c == -1) throw new IOException("EOF before null termination");
      baos.write(c);
    }
    String name = new String(baos.toByteArray(), "UTF-8");
    if (name.length() == 0) {
      throw new IOException("empty name in response");
    }

    // Extract extra info, if any
    baos.reset();
    while ((c = is.read()) != 0) {
      if (c == -1) throw new IOException("EOF before null termination");
      baos.write(c);
    }
    byte[] rawMeta = baos.toByteArray();
    if (rawMeta == null || rawMeta.length == 0) {
      if (is.available() < 16) {
        throw new IOException("not enough room for the GUID");
      }
      return new Response(index, size, name);
    } else {
      // now handle between-the-nulls
      // \u001c is the HUGE v0.93 GEM delimiter
      HUGEExtension huge = new HUGEExtension(rawMeta);

      Set urns = huge.getURNS();

      LimeXMLDocument doc = null;
      Iterator iter = huge.getMiscBlocks().iterator();
      while (iter.hasNext() && doc == null) doc = createXmlDocument(name, (String) iter.next());

      GGEPContainer ggep = GGEPUtil.getGGEP(huge.getGGEP());

      return new Response(index, size, name, urns, doc, ggep, rawMeta);
    }
  }