FileDocCategorySizeDatePackage
MMSAddress.javaAPI DocphoneME MR2 API (J2ME)31312Wed May 02 18:00:44 BST 2007com.sun.midp.io.j2me.mms

MMSAddress.java

/*
 *   
 *
 * Copyright  1990-2007 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation.
 * 
 * This program 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
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt).
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions.
 */

package com.sun.midp.io.j2me.mms;

/**
 * MMS address parsing and validation.
 * MMS address is validated according to Table D-2 in WMA2.0 specification.
 * In addition address is parsed into the fields of <code>MMSAddress </code>
 * object.
 */
public class MMSAddress {

    /**
     * Determines whether a character represents a hexadecimal number.
     * ('0' - '9' and 'A' - 'F')
     * @param c The character to check.
     * @return <code>true </code> if <code>c</code> is a hexadecimal number;
     * <code> false </code> otherwise
     */
    private static boolean hex(char c) {
	return (digit(c) || (c >= 'A' && c <= 'F'));
    }

    /**
     * Determines whether a character is a letter.
     * @param c The character to check.
     * @return <code>true </code> if <code>c</code> is a letter; 
     * <code> false </code> otherwise
     */
    private static boolean alpha(char c) {
	return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
    }

    /**
     * Determines whether a character represents a digit.
     * @param c The character to check.
     * @return <code>true </code> if <code>c</code> is a digit; 
     * <code> false </code> otherwise
     */
    private static boolean digit(char c) {
	return (c >= '0' && c <= '9');
    }

    /**
     * Determines whether a string represents a valid phone number
     * starting from an index. If the address string is a valid
     * phone number representation it is parsed into the fields of 
     * <code>MMSAddress </code>object (address and application id).
     * General phone number syntax is specified in WMA 2.0 spec as :
     * ("+" 1*digit | 1*digit) [applicationId]
     *
     * @param s The string to check.
     * @param mmsAddress The return <code>MMSAddress</code> object which
     * fields are filled with parsed address and
     * application id (which can be null), 
     * its type is set to GLOBAL_PHONE_NUMBER.
     * @param i The index of the string at which to start the check. 
     * <code> 0 <= i <= s.length() </code> 
     *
     * @return <code>true </code> if <code>s</code> represents a valid 
     * phone number starting from index <code>i</code>;
     * <code> false </code> otherwise and if index <code>i</code> is
     * invalide value
     */
    static boolean parsePhoneNumber(String s, int i,
				    MMSAddress mmsAddress) {

	int len = s.length();
	if (i < 0 || i >= len) {
	    return false;
	}

	int initI = i;
	char c = s.charAt(i);

	if (c == '+') {
	    i++;
	    if (len == i) {
		return false;
	    }
	}

	int j = 0;
	for (; i < len; j++, i++) {
	    c = s.charAt(i);
	    if (!digit(c)) {
		break;
	    }
	}
	
	if (j == 0) {
	    return false;
	}

	if (i == len) {
	    mmsAddress.set(s.substring(initI), null, GLOBAL_PHONE_NUMBER);
	    return true;
	}

	if (c == ':') {
	    mmsAddress.set(s.substring(initI, i), 
			   null, GLOBAL_PHONE_NUMBER);
	    return parseApplicationId(s, i, mmsAddress);
	}
	
	return false;
    }

    /**
     * Determines whether a string represents a valid ipv4 address
     * starting from an index. If the address string is a valid
     * ipv4 representation it is parsed into the fields of 
     * <code>MMSAddress </code>object (address and application id).
     * ipv4 syntax is specified in WMA 2.0 spec as :
     * 1*3digit "." 1*3digit "." 1*3digit "." 1*3digit [applicationId]
     *
     * @param s The string to check.
     * @param mmsAddress The return <code>MMSAddress</code> object which
     * fields are filled with parsed address and
     * application id (which can be null), its type is set to IPV4.
     * @param i The index of the string at which to start the check. 
     * <code> 0 <= i <= s.length() </code> 
     *
     * @return <code>true </code> if <code>s</code> represents a valid 
     * ipv4 address starting from index <code>i</code>;
     * <code> false </code> otherwise and if index <code>i</code> is
     * invalide value
     */
    static boolean parseIpv4(String s, int i, MMSAddress mmsAddress) {
	int len = s.length();
	if (i <= 0 || i >= len) {
	    return false;
	}

	char c;
	int j;
	int initI = i;

	for (int num = 1; num < 5; num++) {

	    for (j = 0; j < 3 && i < len; i++, j++) {
		
		c = s.charAt(i);
		
		if (!digit(c)) {
		    break;
		}
	    }
	    
	    // there should be at least one digit and 
	    // the number between dots should be less then 256
	    if (j == 0 || 
		(j == 3 && 
		 (((s.charAt(i-3)-'0')*10 + (s.charAt(i-2)-'0'))*10 +
		  (s.charAt(i-1)-'0')) > 255)) {
		return false;
	    }
	    
	    // check if this is the end of the string
	    if (i == len)  {
		if (num == 4) {
		    mmsAddress.set(s.substring(initI), null, IPV4);
		    return true;
		}
		return false;
	    }

	    c = s.charAt(i);

	    if (c == ':') {
		mmsAddress.set(s.substring(initI, i), null, IPV4);
		return parseApplicationId(s, i, mmsAddress);
	    }

	    if (c != '.') {
		return false; // 4th character after beg (or dot)
		// should be dot;
		// only dots are allowed as non digit char
	    }

	    // allowed '.' => continue
	    i++;
	}
	
	return false;
    }

    /**
     * Determines whether a string represents a valid ipv6 address
     * starting from an index. If the address string is a valid
     * ipv6 representation it is parsed into the fields of 
     * <code>MMSAddress </code>object (address and application id).
     * ipv6 syntax is specified in WMA 2.0 spec as :
     * ipv6-atom ":" ipv6-atom ":" ipv6-atom ":" ipv6-atom ":" 
     * ipv6-atom ":" ipv6-atom ":" ipv6-atom ":" ipv6-atom [appId]
     * where ipv6-atom is 1*4(digit | hex-alpha).
     *
     * @param s The string to check.
     * @param mmsAddress The return <code>MMSAddress</code> object which
     * fields are filled with parsed address and
     * application id (which can be null), its type is set to IPV6.
     * @param i The index of the string at which to start the check. 
     * <code> 0 <= i <= s.length() </code> 
     *
     * @return <code>true </code> if <code>s</code> represents a valid 
     * ipv6 address starting from index <code>i</code>;
     * <code> false </code> otherwise and if index <code>i</code> is
     * invalide value
     */
    static boolean parseIpv6(String s, int i, MMSAddress mmsAddress) {
	int len = s.length();
	if (i <= 0 || i >= len) {
	    return false;
	}

	char c;
	int j;
	int initI = i;

	for (int num = 1; num < 9; num++) {

	    for (j = 0; j < 4 && i < len; i++, j++) {
		
		c = s.charAt(i);
		
		if (!hex(c)) {
		    break;
		}
	    }
	    
	    // there should be at least one digit 
	    if (j == 0) {
		return false;
	    }

	    
	    // check if this is the end of the string
	    if (i == len)  {
		if (num == 8) {
		    mmsAddress.set(s.substring(initI), null, IPV6);
		    return true;
		}
		return false;
	    }

	    c = s.charAt(i);

	    if (c == ':') { 
		if (num == 8) {
		    mmsAddress.set(s.substring(initI, i), 
				   null, IPV6);
		    return parseApplicationId(s, i, mmsAddress);
		}
	    } else {
		return false; // 5th character after beg (or :)
		// should be :
		// only : are allowed as non digit char
	    }

	    // allowed ':' => continue
	    i++;
	}
	
	return false;
    }

    /**
     * Determines whether a string represents a valid shortcode
     * starting from an index. If the address string is a valid
     * shortcode representation it is parsed into the fields of 
     * <code>MMSAddress </code>object (address and null for appId).
     * Shortcode syntax is specified in WMA 2.0 spec as :
     * *(digit | alpha)
     *
     * @param s The string to check.
     * @param mmsAddress The return <code>MMSAddress</code> object which
     * fields are filled with parsed address and application id (null in
     * this case), its type is set to SHORTCODE.
     * @param i The index of the string at which to start the check. 
     * <code> 0 <= i <= s.length() </code> 
     *
     * @return <code>true </code> if <code>s</code> represents a valid 
     * shortcode starting from index <code>i</code>;
     * <code> false </code> otherwise and if index <code>i</code> is
     * invalide value
     */
    static boolean parseShortCode(String s, int i, 
				  MMSAddress mmsAddress) {
	int len = s.length();
	if (i > len) {
	    return false;
	}

	if (i == len) {
            return false; // Even though spec allows "mms://" as a valid 
            // shortcode, we disallow it here
	}
        int initI = i;
	char c;
	
	for (; i < s.length(); i++) {
	    c = s.charAt(i);

	    if (!digit(c) && !alpha(c)) {
		return false;
	    }
	}
	mmsAddress.set(s.substring(initI), null, SHORTCODE);
	return true;
    }
    
    /**
     * Determines whether a string represents a valid application ID
     * starting from an index.If the address string is a valid
     * application id representation it is parsed into the fields of 
     * <code>MMSAddress </code>object (application id and null for the 
     * address).
     * Application ID syntax is specified in WMA 2.0 spec as :
     * ":"[*(1*(alpha | digit | "." | "_") ".")]1*(alpha | digit | "." | "_").
     * The number of characters in application ID must not exceed 32.
     *
     * @param s The string to check.
     * @param mmsAddress The return <code>MMSAddress</code> object which
     * fields are filled with parsed address (null in this case) and
     * application id, its type is set to APP_ID.
     * @param i The index of the string at which to start the check. 
     * <code> 0 <= i <= s.length() </code> 
     *
     * @return <code>true </code> if <code>s</code> represents a valid 
     * application ID starting from index <code>i</code>;
     * <code> false </code> otherwise and if index <code>i</code> is
     * invalide value
     */
    static boolean parseApplicationId(String s, int i, 
				      MMSAddress mmsAddress) {

	int len = s.length();

        /*
	 * Empty string or string like ":" are not allowed.
	 * Only 32 characters are allowed. 
         * Note: When checking appID length, keep in mind that
         * i is pointing to ":"
         */
        if (i <= 0 || i >= len || (len-i-1) > 32 || (len-i-1) < 0 ||
            s.charAt(i) != ':') {
	    return false;
	}
	
	i++; // to skip ':'

	char c;
	int initI = i;

	for (int j = 0; i < len; i++) {
	    
	    c = s.charAt(i);
	    
	    for (j = 0; i < len; i++, j++) {
		c = s.charAt(i);

	        if ((c != '_') && /* dot is also allowed in the spec ??? */
		    !digit(c) && !alpha(c)) {
		    break;
		}
	    }

	    if (i == len) {
		if (mmsAddress.type == INVALID_ADDRESS) {
		    mmsAddress.set(null, s.substring(initI), APP_ID);
		} else {
		    mmsAddress.setAppid(s.substring(initI));
		}
		return true;
	    }

	    // there has to be at least one digit or letter and
	    // nondigit and nonalpha char can be only dot (one dot only)
	    if (j == 0 || c != '.') {
		return false;
	    }
	}
	
	return false;
    }
    
    // ------------------------------- E-mail validation ----------------
    /**
     * Determines whether a character represents a special e-mail character.
     * @param c The character to check.
     * @return <code>true </code> if <code>c</code> is a special character.
     * <code> false </code> otherwise
     */
    private static boolean specials(char c) {
	if (c == '(' || c == ')' || c == '<' || c == '>' ||
	    c == '@' || c == ',' || c == ';' || c == ':' ||
	    c == '\\' || c == '"' || c == '.' || c == '[' || c == ']') {
	    return true;
	}
	return false;
    }

    /**
     * Determines whether a character represents a character.
     * @param c The character to check.
     * @return <code>true </code> if <code>c</code> is a character.
     * <code> false </code> otherwise
     */
    private static boolean isChar(char c) {
	return ((int)c) >= 0 && ((int)c) <= 127;
    }

    /**
     * Determines whether a character represents an ascii control
     * character.
     * @param c The character to check.
     * @return <code>true </code> if <code>c</code> is a character.
     * <code> false </code> otherwise
     */
    private static boolean asciiControl(char c) {
	return ((int)c >= 0 && (int)c <= 31);
    }

    /**
     * Determines whether a character represents an atom character.
     * as specified in WMA 2.0 specification.
     * @param c The character to check.
     * @return <code>true </code> if <code>c</code> is a special character.
     * <code> false </code> otherwise
     */
    private static boolean atomChar(char c) {
        return ((int)c <= 127 && (int)c > 32 && !specials(c));
    }
    

    /**
     * Determines first not linear white space character 
     * in <code>s</code> after character with index <code>i</code> but
     * before character with index <code>n</code>.
     * 
     * @param s The string to check.
     * @param i The index of the string at which to start the check. 
     * <code> 0 <= i <= s.length() </code>
     * @param n The index of the last character to be checked in 
     *          <code>s</code>.
     * @return The first non linear white character after position 
     *          <code>i</code>, it could the the same as passed in
     *          <code>i</code> value if there are no linear white
     *          space characters.
     */
    private static int linearWhiteSpaceSeq(String s, int i, int n) {
	char c;
	for (; i < n; i++) {
	    c = s.charAt(i);
            if (c == ' ' || c == '\t') {
                continue;
            }
	    if (c == '\r') {
		// only CR LF one after another are allowed
		// CR by itself is not allowed
	        if (i == n-1 || s.charAt(i+1) != '\n') {
		    return i;
		}
		i++;
	    } else {
		return i;
	    }
	}

	return i;
    }

    /**
     * Determines whether a string represents a valid e-mail address
     * starting from an index. If the address string is a valid
     * e-mail address representation it is parsed into the fields of 
     * <code>MMSAddress </code>object (address and null for appId).
     * E-mail syntax is specified in WMA 2.0 spec.
     *
     * @param s The string to check.
     * @param i The index of the string at which to start the check. 
     * <code> 0 <= i <= s.length() </code>
     * @param mmsAddress The return <code>MMSAddress</code> object which
     * fields are filled with parsed address and
     * application id (null in this case),  its type is set to EMAIL.
     *
     * @return <code>true </code> if <code>s</code> represents a valid 
     * e-mail address starting from index <code>i</code>;
     * <code> false </code> otherwise and if index <code>i</code> is
     * invalide value
     */
    static boolean parseEmail(String s, int i, MMSAddress mmsAddress) {

	int len = s.length();
	if (i <= 0 || i >= len) {
	    return false;
	}

	int initI = i;

	// email can be on of the following:
	// emial ::== mailbox | phrase : [#mailbox] ;

	// if there is a ';' at the end then there should be a phrase
	// at the beginning
	if (s.charAt(len-1) == ';') {

	    if ((i = isPhrase(s, i, len-1)) <= 0) {
		return false;
	    }

	    // there was no ":"
	    if (i >= len-1 || s.charAt(i) != ':') {
		return false;
	    }

	    i++; // move after ':'

	    if (i == len-1) {
                mmsAddress.set(s.substring(initI), null, EMAIL);
		return true; // phrase : ; is allowed string
	    }

	    if ((i = isMailbox(s, i, len-1)) <= 0) {
		return false;
	    }

	    while (i < len-1) {

		// there has to be a ',' sep
		if (i >= len-2 && s.charAt(i) != ',') {
		    return false;
		}

		if ((i = isMailbox(s, i+1, len-1)) <= 0) {
		    return false;
		}
	    }
	    if (i == len-1) {
		mmsAddress.set(s.substring(initI), null, EMAIL);
		return true;
	    }
	    return false;
	}

	if (isMailbox(s, i, len) == len) {
	    mmsAddress.set(s.substring(initI), null, EMAIL);
	    return true;
	}
	return false;
    }

    /**
     * Determines whether a string represents a valid mailbox address
     * starting from an index.
     * Mailbox syntax is specified in WMA 2.0 spec as
     * local-part"@"domain  or as
     * [phrase] "<"[("@"domain) [*("," ("@"domain))]] local-part"@"domain">"
     *
     * @param s The string to check.
     * @param i The index of the string at which to start the check. 
     * <code> 0 <= i <= s.length() </code> 
     * @param n The index of the last character to be checked in 
     *          <code>s</code>.
     *
     * @return index till which 
     * <code>s</code> contains valid mailbox specification;
     * <code> -1 </code> if index <code>i</code> is invalid or
     * if characters in <code>s</code> starting from <code>i</code>
     * do not comply with mailbox specification
     */
    private static int isMailbox(String s, int i, int n) {
        if (n > s.length()) {
            n = s.length();
        }
	if (i >= n) {
	    return -1;
	}

	int initI = i;

	if ((i = isLocalAtDomain(s, initI, n)) > 0) {
	    return i;
	}

	i = initI;

	// check for  [phrase] <[1#(@ domain):] local @ domain>
	// note phrase is optional
	if ((s.charAt(i) != '<') &&
	    (i = isPhrase(s, initI, n)) < 0) {
	    return -1;
	}

	if (i == n || s.charAt(i) != '<') {
	    return -1; 
	}

	i++;
	    
	if (i == n) {
	    return -1;
	}

	// there can be a list of domains: 
	// @ domain, @domain, @domain :
	boolean atLeastOneDomain = false;
	while (s.charAt(i) == '@') {
	    atLeastOneDomain = true;

	    if ((i = isDomain(s, i+1, n)) < 0) {
		return -1;
	    }

	    if (i == n) {
		return -1;
	    }
	    
	    if (s.charAt(i) == ':') {
		break;
	    }

	    // @domain,,@domain is allowed
	    while (s.charAt(i) == ',') {
		i++;
		if (i == n || s.charAt(i) == ':') {
		    return -1;
		}
	    }
	}

	if (atLeastOneDomain) {
	    if (i == n || s.charAt(i) != ':') {
		return -1;
	    }
	    i++;
	}

	// local @ domain
	if ((i = isLocalAtDomain(s, i, n)) <= 0) {
	    return -1;
	}

	if (i < n && s.charAt(i) == '>') {
	    return i + 1;
	}
	return -1;
    }

    /**
     * Determines whether a string represents a valid "local-part"
     * starting from an index.
     * Local-part syntax is specified in WMA 2.0 spec as
     * (atom | "\"alpha) *("." (atom | "\"alpha)) where 
     * atom is represented by at least one char which is 
     * not a space or a special or control character.
     *
     * @param s The string to check.
     * @param i The index of the string at which to start the check
     * <code> 0 <= i <= s.length() </code> 
     * @param n The index of the last character to be checked in 
     *          <code>s</code>.
     *
     * @return index till which 
     * <code>s</code> contains valid local-part specification;
     * <code> -1 </code> if index <code>i</code> is invalid or
     * if characters in <code>s</code> starting from <code>i</code>
     * do not comply with local-part specification
     */
    private static int isLocalPart(String s, int i, int n) {
	return isSequenceOfAtomAndText(s, i, n, '"', '"', true);
    }


    /**
     * Determines whether a string represents a valid "phrase"
     * starting from an index.
     * Phrase syntax is specified in WMA 2.0 spec as
     * 1*(atom | """ *(text w/o ",\,\r but with linear space | "\"alpha) """) 
     * where atom is represented by at least one char or more which is 
     * not a space or a special or control character.
     *
     * @param s The string to check.
     * @param i The index of the string at which to start the check
     * <code> 0 <= i <= s.length() </code> 
     * @param n The index of the last character to be checked in 
     *          <code>s</code>.
     *
     * @return index till which 
     * <code>s</code> contains valid phrase specification;
     * <code> -1 </code> if index <code>i</code> is invalid or
     * if characters in <code>s</code> starting from <code>i</code>
     * do not comply with phrase specification
     */
    private static int isPhrase(String s, int i, int n) {
	return isSequenceOfAtomAndText(s, i, n, '"', '"', false);
    }


    /**
     * Determines whether a string represents a valid "domain"
     * starting from an index.
     * Domain syntax is specified in WMA 2.0 spec as
     * 1*(atom | "[" *(text w/o ",[,],\r but with linear space | "\"alpha) "]")
     * where atom is represented by at least one char or more which is 
     * not a space or a special or control character.
     *
     * @param s The string to check.
     * @param i The index of the string at which to start the check
     * <code> 0 <= i <= s.length() </code> 
     * @param n The index of the last character to be checked in 
     *          <code>s</code>.
     *
     * @return index till which 
     * <code>s</code> contains valid domain specification;
     * <code> -1 </code> if index <code>i</code> is invalid or
     * if characters in <code>s</code> starting from <code>i</code>
     * do not comply with domain specification
     */
    private static int isDomain(String s, int i, int n) {
	return isSequenceOfAtomAndText(s, i, n, '[', ']', true);
    }


    /**
     * Determines whether a string starting from an index
     * represents a valid sequence that satisfies the following
     * syntax:
     * text ::== <begSep>
     *           *(1*(<any char w/o begSep and eSep incl LWSP>|\ALPHA))
     *           <endSep>
     * sequence ::== 1*(atom | text) | (atom | text) *("." (atom | text))
     * where atom is represented by at least one or more characters that  
     * are not space or special or control characters
     * and begSep/endSep are separators that are placed around text.
     * LWSP is a linear white space (space and tabs and \r\n but not \r).
     *
     * @param s The string to check.
     * @param i The index of the string at which to start the check
     * <code> 0 <= i <= s.length() </code> 
     * @param n The index of the last character to be checked in 
     *          <code>s</code>.
     * @param begSep The character marking beginning of quoted text
     * @param endSep The character marking the end of the text
     * @param dotSeparated If true atom and quoted text should be 
     *        separated by a "." character; ".." is not allowed
     *
     * @return index till which 
     * <code>s</code> contains valid sequence;
     * <code> -1 </code> if index <code>i</code> is invalid or
     * if characters in <code>s</code> starting from <code>i</code>
     * do not comply with the sequence specification.
     */
    private static int isSequenceOfAtomAndText(String s, int i, int n, 
					       char begSep, char endSep, 
					       boolean dotSeparated) { 
	if (i >= s.length()) {
	    return -1;
	}
        if (n > s.length()) {
            n = s.length();
        }

	// see if there is at least one word
	boolean withinQuotation = false;
	boolean atLeastOneWord  = false;

	char c;
	int initI = i;

	for (; i < n; i++) {
	    c = s.charAt(i);
  	    if (c == begSep) {
		if (begSep == endSep) {
		    withinQuotation = !withinQuotation;
		} else if (withinQuotation) {
		    return -1;
		} else {
		    withinQuotation = true;
		}
		atLeastOneWord = true;
	    } else if (c == endSep) {
		if (!withinQuotation) {
		    return -1;
		}
		withinQuotation = false;
	    } else if (c == '\\') {
		// chars can be quoated only within separators
		// and there has to be another char after '\'
		if (!withinQuotation || i == n-1) {
		    return -1;
		}
		i++;
		c = s.charAt(i);
		// only ALPHA character can be quoted
		if (!alpha(c)) {
		    return -1;
		}

	    } else if (dotSeparated && c == '.') {
		if (i == initI ||
		    ((i - initI) > 0 && s.charAt(i-1) == '.')) {
		    // empty word
		    // string starts with .
		    return -1;
		}
	    } else if (withinQuotation) {
		// within quotation \r is not allowed but
		// \r is allowed as part of linear-white-space
		if (c == ' ' || c == '\t' || c == '\r') {
		    if ((i = linearWhiteSpaceSeq(s, i, n)) < 0) {
			return -1;
		    }
		    i--;
		}
	    } else if (atomChar(c)) {
		atLeastOneWord = true;
	    } else {
		// this is supposed to be an atom 
		break;
	    }
	}

	// open quotation or no words or
	// dot separator is the last char
	if (withinQuotation || !atLeastOneWord ||
	    (dotSeparated && i > initI && s.charAt(i-1) == '.')) {
	    return -1;
	}

	return i;
    }

    /**
     * Determines whether a string starting from an index satisfies the
     * following syntax: local-part"@"domain
     *
     * @param s The string to check.
     * @param i The index of the string at which to start the check. 
     * <code> 0 <= i <= s.length() </code> 
     * @param n The index of the last character to be checked in 
     *          <code>s</code>.
     *
     * @return index till which 
     * <code>s</code> contains valid mailbox specification;
     * <code> -1 </code> if index <code>i</code> is invalid or
     * if characters in <code>s</code> starting from <code>i</code>
     * is not in the form of local-part"@"domain
     */
    private static int isLocalAtDomain(String s, int i, int n) {
        if (n > s.length()) {
            n = s.length();
        }
	if (i >= n) {
	    return -1;
	}

	// local
	if ((i = isLocalPart(s, i, n)) <= 0) {
	    return -1;
	}

	if (i == n || s.charAt(i) != '@') {
	    return -1;
	}

        i++;

	// domain
	return isDomain(s, i, n);
    }

    /**
     * Determines whether a string represents a valid mms address
     * as specified in WMA 2.0 specification and parses the
     * incoming string into address and application id strings.
     *
     * @param addressStr The string to check.
     *
     * @return newly created <code>MMSAddress </code> object 
     * if <code>addressStr</code> is a valid mms address; 
     * <code> null </code> otherwise
     */
    public static MMSAddress getParsedMMSAddress(String addressStr) {
	return getParsedMMSAddress(addressStr, null);
    }

    /**
     * Determines whether a string represents a valid mms address
     * as specified in WMA 2.0 specification and parses the
     * incoming string into address and application id strings.
     * 
     * @param addressStr The string to check.
     * @param mmsAddress The return <code>MMSAddress</code> object which
     * fields will be filled with parsed address and
     * application id, and correspoinding type (EMAIL, GLOBAL_PHONE_NUMBER,
     * IPV4, IPV6, SHORTCODE, APP_ID).
     *
     * @return <code>MMSAddress </code> object if <code>addressStr</code> 
     * is a valid mms address; <code> null </code> otherwise
     */
    public static MMSAddress getParsedMMSAddress(String addressStr, 
						 MMSAddress mmsAddress) {

	if (addressStr == null || !addressStr.startsWith("mms://")) {
	    return null;
	}

        if (mmsAddress == null) {
            mmsAddress = new MMSAddress();
        }

	if (parsePhoneNumber(addressStr, 6, mmsAddress) || 
	    parseIpv4(addressStr, 6, mmsAddress) || 
	    parseIpv6(addressStr, 6, mmsAddress) || 
	    parseEmail(addressStr, 6, mmsAddress) ||
	    parseShortCode(addressStr, 6, mmsAddress) || 
	    parseApplicationId(addressStr, 6, mmsAddress)) {
	    return mmsAddress;
	}

	return null;
    }


    /** Type corresponding to invalid address. */
    public static final int INVALID_ADDRESS     = -1;

    /** Type corresponding to the e-mail address. */
    public static final int EMAIL               = 0;

    /** Type corresponding to global phone number address. */
    public static final int GLOBAL_PHONE_NUMBER = 1;

    /** Type corresponding to the ipv4 address. */
    public static final int IPV4                = 2;

    /** Type corresponding to the ipv6 address. */
    public static final int IPV6                = 3;

    /** Type corresponding to the shortcode address. */
    public static final int SHORTCODE           = 4;

    /** Type corresponding to the application id address. */
    public static final int APP_ID              = 5;

    /** 
     * Field that holds address part of the mms address 
     * (without "mms://" and ":" separator before app id).
     */
    public String address;

    /** 
     * Field that holds application id part of the mms address
     * which appears after "mms://:" or 
     * after phone number, ipv4, or ipv6 followed by ":" .
     */
    public String appId;

    /** 
     * Type of this MMSAddress instance, 
     * <code>INVALID_ADDRESS</code> when uninitialized .
     */
    public int type;

    /**
     *  MMSAddress constructor to create uninitialized instance.
     */
    MMSAddress() {
	clear();
    }

    /**
     * MMSAddress constructor to create initialized instance.
     * @param address The address part of the mms address
     * @param appId The application id part of the mms address
     * @param type The type of this mms addreess
     * (EMAIL, GLOBAL_PHONE_NUMBER, IPV4, IPV6, SHORTCODE, APP_ID) 
     */
    MMSAddress(String address, String appId, int type) {
	set(address, appId, type);
    }

    /**
     * Clears <code>MMSAddress</code> fields.
     * Type is set to INVALID_ADDRESS while
     * address and appId are set to null.
     */
    void clear() {
	address = appId = null;
	type = INVALID_ADDRESS;
    }

    /**
     * Sets <code>MMSAddress</code> fields to the passed in values.
     * @param address The address part of the mms address
     * @param appId The application id part of the mms address
     * @param type The type of this mms addreess
     * (EMAIL, GLOBAL_PHONE_NUMBER, IPV4, IPV6, SHORTCODE, APP_ID) 
     */
    void set(String address, String appId, int type) {
	this.address = address;
	this.appId   = appId;
	this.type    = type;
    }

    /**
     * Sets <code>MMSAddress</code> application id field to 
     * the passed in value.
     * @param appId The application id part of the mms address
     */
    void setAppid(String appId) {
	this.appId = appId; 
    }

    /**
     * Creates a valid mms address corresponding to the values
     * in this <code>MMSAddress</code> object.
     * If the object is not intialized null is returned.
     * @return mms address corresponding to this MMSAddress object.
     */
    String getMMSAddressString() {

	if (type == INVALID_ADDRESS || 
	    (address == null && appId == null)) {
	    return null;
	}

	StringBuffer mmsAddr = new StringBuffer("mms://");
	if (address != null) mmsAddr.append(address);
	if (appId != null) mmsAddr.append(":").append(appId);
	return mmsAddr.toString();
    }
}