private Property extractVcard(StringBuilder out) throws IOException, ParseException {
    Property prop;

    out.append(BEGIN_VCARD).append(CRLF);

    do {
      prop = mParser.next();
      out.append(prop).append(CRLF);
    } while (!prop.equals(END_VCARD));

    return mParser.next();
  }
  private Property parseEnvelope(int level) throws IOException, ParseException {

    Property prop;

    /*
     * we can support as many nesting level as we want, but MAP spec clearly
     * defines that there should be no more than 3 levels. so we verify it
     * here.
     */

    if (level > 3) {
      throw new ParseException("bEnvelope is nested more than 3 times", mParser.pos());
    }

    /*
     * <bmessage-envelope> ::= { "BEGIN:BENV" <CRLF> [<bmessage-recipient>]*
     * <bmessage-envelope> | <bmessage-content> "END:BENV" <CRLF> }
     */

    prop = mParser.next();

    while (prop.equals(BEGIN_VCARD)) {

      /* <bmessage-originator>::= <vcard> <CRLF> */

      StringBuilder vcard = new StringBuilder();
      prop = extractVcard(vcard);

      if (level == 1) {
        VCardEntry entry = parseVcard(vcard.toString());
        mBmsg.mRecipients.add(entry);
      }
    }

    if (prop.equals(BEGIN_BENV)) {
      prop = parseEnvelope(level + 1);

    } else if (prop.equals(BEGIN_BBODY)) {
      prop = parseBody();

    } else {
      throw expected(BEGIN_BENV, BEGIN_BBODY);
    }

    if (!prop.equals(END_BENV)) {
      throw expected(END_BENV);
    }

    return mParser.next();
  }
  private void parse(String str) throws IOException, ParseException {

    Property prop;

    /*
     * <bmessage-object>::= { "BEGIN:BMSG" <CRLF> <bmessage-property>
     * [<bmessage-originator>]* <bmessage-envelope> "END:BMSG" <CRLF> }
     */

    mParser = new BmsgTokenizer(str + CRLF);

    prop = mParser.next();
    if (!prop.equals(BEGIN_BMSG)) {
      throw expected(BEGIN_BMSG);
    }

    prop = parseProperties();

    while (prop.equals(BEGIN_VCARD)) {

      /* <bmessage-originator>::= <vcard> <CRLF> */

      StringBuilder vcard = new StringBuilder();
      prop = extractVcard(vcard);

      VCardEntry entry = parseVcard(vcard.toString());
      mBmsg.mOriginators.add(entry);
    }

    if (!prop.equals(BEGIN_BENV)) {
      throw expected(BEGIN_BENV);
    }

    prop = parseEnvelope(1);

    if (!prop.equals(END_BMSG)) {
      throw expected(END_BENV);
    }

    /*
     * there should be no meaningful data left in stream here so we just
     * ignore whatever is left
     */

    mParser = null;
  }
  private Property parseProperties() throws ParseException {

    Property prop;

    /*
     * <bmessage-property>::=<bmessage-version-property>
     * <bmessage-readstatus-property> <bmessage-type-property>
     * <bmessage-folder-property> <bmessage-version-property>::="VERSION:"
     * <common-digit>*"."<common-digit>* <CRLF>
     * <bmessage-readstatus-property>::="STATUS:" 'readstatus' <CRLF>
     * <bmessage-type-property>::="TYPE:" 'type' <CRLF>
     * <bmessage-folder-property>::="FOLDER:" 'foldername' <CRLF>
     */

    do {
      prop = mParser.next();

      if (prop.name.equals("VERSION")) {
        mBmsg.mBmsgVersion = prop.value;

      } else if (prop.name.equals("STATUS")) {
        for (Status s : Status.values()) {
          if (prop.value.equals(s.toString())) {
            mBmsg.mBmsgStatus = s;
            break;
          }
        }

      } else if (prop.name.equals("TYPE")) {
        for (Type t : Type.values()) {
          if (prop.value.equals(t.toString())) {
            mBmsg.mBmsgType = t;
            break;
          }
        }

      } else if (prop.name.equals("FOLDER")) {
        mBmsg.mBmsgFolder = prop.value;
      }

    } while (!prop.equals(BEGIN_VCARD) && !prop.equals(BEGIN_BENV));

    return prop;
  }
  private Property parseBody() throws IOException, ParseException {

    Property prop;

    /*
     * <bmessage-content>::= { "BEGIN:BBODY"<CRLF> [<bmessage-body-part-ID>
     * <CRLF>] <bmessage-body-property> <bmessage-body-content>* <CRLF>
     * "END:BBODY"<CRLF> } <bmessage-body-part-ID>::="PARTID:" 'Part-ID'
     * <bmessage-body-property>::=[<bmessage-body-encoding-property>]
     * [<bmessage-body-charset-property>]
     * [<bmessage-body-language-property>]
     * <bmessage-body-content-length-property>
     * <bmessage-body-encoding-property>::="ENCODING:"'encoding' <CRLF>
     * <bmessage-body-charset-property>::="CHARSET:"'charset' <CRLF>
     * <bmessage-body-language-property>::="LANGUAGE:"'language' <CRLF>
     * <bmessage-body-content-length-property>::= "LENGTH:" <common-digit>*
     * <CRLF>
     */

    do {
      prop = mParser.next();

      if (prop.name.equals("PARTID")) {
      } else if (prop.name.equals("ENCODING")) {
        mBmsg.mBbodyEncoding = prop.value;

      } else if (prop.name.equals("CHARSET")) {
        mBmsg.mBbodyCharset = prop.value;

      } else if (prop.name.equals("LANGUAGE")) {
        mBmsg.mBbodyLanguage = prop.value;

      } else if (prop.name.equals("LENGTH")) {
        try {
          mBmsg.mBbodyLength = Integer.valueOf(prop.value);
        } catch (NumberFormatException e) {
          throw new ParseException("Invalid LENGTH value", mParser.pos());
        }
      }

    } while (!prop.equals(BEGIN_MSG));

    /*
     * <bmessage-body-content>::={ "BEGIN:MSG"<CRLF> 'message'<CRLF>
     * "END:MSG"<CRLF> }
     */

    int messageLen = mBmsg.mBbodyLength - MSG_CONTAINER_LEN;
    int offset = messageLen + CRLF_LEN;
    int restartPos = mParser.pos() + offset;

    /*
     * length is specified in bytes so we need to convert from unicode
     * string back to bytes array
     */

    String remng = mParser.remaining();
    byte[] data = remng.getBytes();

    /* restart parsing from after 'message'<CRLF> */
    mParser = new BmsgTokenizer(new String(data, offset, data.length - offset), restartPos);

    prop = mParser.next(true);

    if (prop != null && prop.equals(END_MSG)) {
      mBmsg.mMessage = new String(data, 0, messageLen);
    } else {

      data = null;

      /*
       * now we check if bMessage can be parsed if LENGTH is handled as
       * number of characters instead of number of bytes
       */

      Log.w(TAG, "byte LENGTH seems to be invalid, trying with char length");

      mParser = new BmsgTokenizer(remng.substring(offset));

      prop = mParser.next();

      if (!prop.equals(END_MSG)) {
        throw expected(END_MSG);
      }

      mBmsg.mMessage = remng.substring(0, messageLen);
    }

    prop = mParser.next();

    if (!prop.equals(END_BBODY)) {
      throw expected(END_BBODY);
    }

    return mParser.next();
  }