1345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein/*
2345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Copyright (C) 2008 The Android Open Source Project
3345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein *
4345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Licensed under the Apache License, Version 2.0 (the "License");
5345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * you may not use this file except in compliance with the License.
6345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * You may obtain a copy of the License at
7345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein *
8345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein *      http://www.apache.org/licenses/LICENSE-2.0
9345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein *
10345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Unless required by applicable law or agreed to in writing, software
11345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * distributed under the License is distributed on an "AS IS" BASIS,
12345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * See the License for the specific language governing permissions and
14345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * limitations under the License.
15345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */
16345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinpackage com.android.emailcommon.mail;
17345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
18821e578a71c7015646522e729600618f0ec16fc0Tony Mantlerimport android.os.Parcel;
19821e578a71c7015646522e729600618f0ec16fc0Tony Mantlerimport android.os.Parcelable;
20821e578a71c7015646522e729600618f0ec16fc0Tony Mantlerimport android.text.Html;
21345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport android.text.TextUtils;
22345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport android.text.util.Rfc822Token;
23345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport android.text.util.Rfc822Tokenizer;
24345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
25821e578a71c7015646522e729600618f0ec16fc0Tony Mantlerimport com.android.mail.utils.LogTag;
26821e578a71c7015646522e729600618f0ec16fc0Tony Mantlerimport com.android.mail.utils.LogUtils;
27345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport com.google.common.annotations.VisibleForTesting;
28345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
29345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport org.apache.james.mime4j.codec.EncoderUtil;
30345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport org.apache.james.mime4j.decoder.DecoderUtil;
31345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
32345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport java.util.ArrayList;
33345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport java.util.regex.Pattern;
34345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
35345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein/**
36345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * This class represent email address.
37345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein *
38345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * RFC822 email address may have following format.
39345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein *   "name" <address> (comment)
40345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein *   "name" <address>
41345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein *   name <address>
42345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein *   address
43345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Name and comment part should be MIME/base64 encoded in header if necessary.
44345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein *
45345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */
46821e578a71c7015646522e729600618f0ec16fc0Tony Mantlerpublic class Address implements Parcelable {
47821e578a71c7015646522e729600618f0ec16fc0Tony Mantler    public static final String ADDRESS_DELIMETER = ",";
48345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
49345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     *  Address part, in the form local_part@domain_part. No surrounding angle brackets.
50345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
51345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    private String mAddress;
52345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
53345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
54345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Name part. No surrounding double quote, and no MIME/base64 encoding.
55345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * This must be null if Address has no name part.
56345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
57345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    private String mPersonal;
58345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
59821e578a71c7015646522e729600618f0ec16fc0Tony Mantler    /**
60821e578a71c7015646522e729600618f0ec16fc0Tony Mantler     * When personal is set, it will return the first token of the personal
61821e578a71c7015646522e729600618f0ec16fc0Tony Mantler     * string. Otherwise, it will return the e-mail address up to the '@' sign.
62821e578a71c7015646522e729600618f0ec16fc0Tony Mantler     */
63821e578a71c7015646522e729600618f0ec16fc0Tony Mantler    private String mSimplifiedName;
64821e578a71c7015646522e729600618f0ec16fc0Tony Mantler
65345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    // Regex that matches address surrounded by '<>' optionally. '^<?([^>]+)>?$'
66345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    private static final Pattern REMOVE_OPTIONAL_BRACKET = Pattern.compile("^<?([^>]+)>?$");
67345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    // Regex that matches personal name surrounded by '""' optionally. '^"?([^"]+)"?$'
68345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    private static final Pattern REMOVE_OPTIONAL_DQUOTE = Pattern.compile("^\"?([^\"]*)\"?$");
69345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    // Regex that matches escaped character '\\([\\"])'
70345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    private static final Pattern UNQUOTE = Pattern.compile("\\\\([\\\\\"])");
71345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
72f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu    // TODO: LOCAL_PART and DOMAIN_PART_PART are too permissive and can be improved.
73f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu    // TODO: Fix this to better constrain comments.
74f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu    /** Regex for the local part of an email address. */
75f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu    private static final String LOCAL_PART = "[^@]+";
76f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu    /** Regex for each part of the domain part, i.e. the thing between the dots. */
77f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu    private static final String DOMAIN_PART_PART = "[[\\w][\\d]\\-\\(\\)\\[\\]]+";
78f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu    /** Regex for the domain part, which is two or more {@link #DOMAIN_PART_PART} separated by . */
79f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu    private static final String DOMAIN_PART =
80f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu            "(" + DOMAIN_PART_PART + "\\.)+" + DOMAIN_PART_PART;
81f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu
82f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu    /** Pattern to check if an email address is valid. */
83f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu    private static final Pattern EMAIL_ADDRESS =
84f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu            Pattern.compile("\\A" + LOCAL_PART + "@" + DOMAIN_PART + "\\z");
85f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu
86345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    private static final Address[] EMPTY_ADDRESS_ARRAY = new Address[0];
87345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
884d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux    // delimiters are chars that do not appear in an email address, used by fromHeader
89345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    private static final char LIST_DELIMITER_EMAIL = '\1';
90345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    private static final char LIST_DELIMITER_PERSONAL = '\2';
91345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
92821e578a71c7015646522e729600618f0ec16fc0Tony Mantler    private static final String LOG_TAG = LogTag.getLogTag();
93821e578a71c7015646522e729600618f0ec16fc0Tony Mantler
94ede08646dbe563c90358ffec2c7397616ca1e523Scott Kennedy    @VisibleForTesting
95821e578a71c7015646522e729600618f0ec16fc0Tony Mantler    public Address(String address) {
96345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        setAddress(address);
97345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
98345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
99821e578a71c7015646522e729600618f0ec16fc0Tony Mantler    public Address(String address, String personal) {
100821e578a71c7015646522e729600618f0ec16fc0Tony Mantler        setPersonal(personal);
101345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        setAddress(address);
102345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
103345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
104821e578a71c7015646522e729600618f0ec16fc0Tony Mantler    /**
105821e578a71c7015646522e729600618f0ec16fc0Tony Mantler     * Returns a simplified string for this e-mail address.
106821e578a71c7015646522e729600618f0ec16fc0Tony Mantler     * When a name is known, it will return the first token of that name. Otherwise, it will
107821e578a71c7015646522e729600618f0ec16fc0Tony Mantler     * return the e-mail address up to the '@' sign.
108821e578a71c7015646522e729600618f0ec16fc0Tony Mantler     */
109821e578a71c7015646522e729600618f0ec16fc0Tony Mantler    public String getSimplifiedName() {
110821e578a71c7015646522e729600618f0ec16fc0Tony Mantler        if (mSimplifiedName == null) {
111821e578a71c7015646522e729600618f0ec16fc0Tony Mantler            if (TextUtils.isEmpty(mPersonal) && !TextUtils.isEmpty(mAddress)) {
112821e578a71c7015646522e729600618f0ec16fc0Tony Mantler                int atSign = mAddress.indexOf('@');
113821e578a71c7015646522e729600618f0ec16fc0Tony Mantler                mSimplifiedName = (atSign != -1) ? mAddress.substring(0, atSign) : "";
114821e578a71c7015646522e729600618f0ec16fc0Tony Mantler            } else if (!TextUtils.isEmpty(mPersonal)) {
115821e578a71c7015646522e729600618f0ec16fc0Tony Mantler
116821e578a71c7015646522e729600618f0ec16fc0Tony Mantler                // TODO: use Contacts' NameSplitter for more reliable first-name extraction
117821e578a71c7015646522e729600618f0ec16fc0Tony Mantler
118821e578a71c7015646522e729600618f0ec16fc0Tony Mantler                int end = mPersonal.indexOf(' ');
119821e578a71c7015646522e729600618f0ec16fc0Tony Mantler                while (end > 0 && mPersonal.charAt(end - 1) == ',') {
120821e578a71c7015646522e729600618f0ec16fc0Tony Mantler                    end--;
121821e578a71c7015646522e729600618f0ec16fc0Tony Mantler                }
122821e578a71c7015646522e729600618f0ec16fc0Tony Mantler                mSimplifiedName = (end < 1) ? mPersonal : mPersonal.substring(0, end);
123821e578a71c7015646522e729600618f0ec16fc0Tony Mantler
124821e578a71c7015646522e729600618f0ec16fc0Tony Mantler            } else {
125821e578a71c7015646522e729600618f0ec16fc0Tony Mantler                LogUtils.w(LOG_TAG, "Unable to get a simplified name");
126821e578a71c7015646522e729600618f0ec16fc0Tony Mantler                mSimplifiedName = "";
127821e578a71c7015646522e729600618f0ec16fc0Tony Mantler            }
128821e578a71c7015646522e729600618f0ec16fc0Tony Mantler        }
129821e578a71c7015646522e729600618f0ec16fc0Tony Mantler        return mSimplifiedName;
130821e578a71c7015646522e729600618f0ec16fc0Tony Mantler    }
131821e578a71c7015646522e729600618f0ec16fc0Tony Mantler
132821e578a71c7015646522e729600618f0ec16fc0Tony Mantler    public static synchronized Address getEmailAddress(String rawAddress) {
133821e578a71c7015646522e729600618f0ec16fc0Tony Mantler        if (TextUtils.isEmpty(rawAddress)) {
134821e578a71c7015646522e729600618f0ec16fc0Tony Mantler            return null;
135821e578a71c7015646522e729600618f0ec16fc0Tony Mantler        }
136821e578a71c7015646522e729600618f0ec16fc0Tony Mantler        String name, address;
137821e578a71c7015646522e729600618f0ec16fc0Tony Mantler        final Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(rawAddress);
138821e578a71c7015646522e729600618f0ec16fc0Tony Mantler        if (tokens.length > 0) {
139821e578a71c7015646522e729600618f0ec16fc0Tony Mantler            final String tokenizedName = tokens[0].getName();
140821e578a71c7015646522e729600618f0ec16fc0Tony Mantler            name = tokenizedName != null ? Html.fromHtml(tokenizedName.trim()).toString()
141821e578a71c7015646522e729600618f0ec16fc0Tony Mantler                    : "";
142821e578a71c7015646522e729600618f0ec16fc0Tony Mantler            address = Html.fromHtml(tokens[0].getAddress()).toString();
143821e578a71c7015646522e729600618f0ec16fc0Tony Mantler        } else {
144821e578a71c7015646522e729600618f0ec16fc0Tony Mantler            name = "";
145821e578a71c7015646522e729600618f0ec16fc0Tony Mantler            address = rawAddress == null ?
146821e578a71c7015646522e729600618f0ec16fc0Tony Mantler                    "" : Html.fromHtml(rawAddress).toString();
147821e578a71c7015646522e729600618f0ec16fc0Tony Mantler        }
148821e578a71c7015646522e729600618f0ec16fc0Tony Mantler        return new Address(address, name);
149821e578a71c7015646522e729600618f0ec16fc0Tony Mantler    }
150821e578a71c7015646522e729600618f0ec16fc0Tony Mantler
151345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public String getAddress() {
152345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        return mAddress;
153345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
154345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
155345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public void setAddress(String address) {
156345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        mAddress = REMOVE_OPTIONAL_BRACKET.matcher(address).replaceAll("$1");
157345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
158345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
159345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
160345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Get name part as UTF-16 string. No surrounding double quote, and no MIME/base64 encoding.
161345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     *
162345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @return Name part of email address. Returns null if it is omitted.
163345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
164345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public String getPersonal() {
165345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        return mPersonal;
166345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
167345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
168345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
169821e578a71c7015646522e729600618f0ec16fc0Tony Mantler     * Set personal part from UTF-16 string. Optional surrounding double quote will be removed.
170345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * It will be also unquoted and MIME/base64 decoded.
171345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     *
172345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @param personal name part of email address as UTF-16 string. Null is acceptable.
173345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
174345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public void setPersonal(String personal) {
175821e578a71c7015646522e729600618f0ec16fc0Tony Mantler        mPersonal = decodeAddressPersonal(personal);
176821e578a71c7015646522e729600618f0ec16fc0Tony Mantler    }
177821e578a71c7015646522e729600618f0ec16fc0Tony Mantler
178821e578a71c7015646522e729600618f0ec16fc0Tony Mantler    /**
179821e578a71c7015646522e729600618f0ec16fc0Tony Mantler     * Decodes name from UTF-16 string. Optional surrounding double quote will be removed.
180821e578a71c7015646522e729600618f0ec16fc0Tony Mantler     * It will be also unquoted and MIME/base64 decoded.
181821e578a71c7015646522e729600618f0ec16fc0Tony Mantler     *
182821e578a71c7015646522e729600618f0ec16fc0Tony Mantler     * @param personal name part of email address as UTF-16 string. Null is acceptable.
183821e578a71c7015646522e729600618f0ec16fc0Tony Mantler     */
184821e578a71c7015646522e729600618f0ec16fc0Tony Mantler    public static String decodeAddressPersonal(String personal) {
185345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (personal != null) {
186345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            personal = REMOVE_OPTIONAL_DQUOTE.matcher(personal).replaceAll("$1");
187345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            personal = UNQUOTE.matcher(personal).replaceAll("$1");
188345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            personal = DecoderUtil.decodeEncodedWords(personal);
189345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            if (personal.length() == 0) {
190345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                personal = null;
191345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            }
192345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
193821e578a71c7015646522e729600618f0ec16fc0Tony Mantler        return personal;
194345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
195345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
196345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
197345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * This method is used to check that all the addresses that the user
198345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * entered in a list (e.g. To:) are valid, so that none is dropped.
199345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
20023c95321afcccdfebcaff33f8be07a07b8c435d7Scott Kennedy    @VisibleForTesting
201345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public static boolean isAllValid(String addressList) {
202345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        // This code mimics the parse() method below.
203345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        // I don't know how to better avoid the code-duplication.
204345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (addressList != null && addressList.length() > 0) {
205345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(addressList);
206345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            for (int i = 0, length = tokens.length; i < length; ++i) {
207345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                Rfc822Token token = tokens[i];
208345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                String address = token.getAddress();
209345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                if (!TextUtils.isEmpty(address) && !isValidAddress(address)) {
210345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                    return false;
211345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                }
212345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            }
213345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
214345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        return true;
215345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
216345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
217345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
218345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Parse a comma-delimited list of addresses in RFC822 format and return an
219345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * array of Address objects.
220345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     *
221345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @param addressList Address list in comma-delimited string.
222345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @return An array of 0 or more Addresses.
223345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
224345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public static Address[] parse(String addressList) {
225345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (addressList == null || addressList.length() == 0) {
226345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return EMPTY_ADDRESS_ARRAY;
227345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
228345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(addressList);
229345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        ArrayList<Address> addresses = new ArrayList<Address>();
230345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        for (int i = 0, length = tokens.length; i < length; ++i) {
231345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            Rfc822Token token = tokens[i];
232345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            String address = token.getAddress();
233345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            if (!TextUtils.isEmpty(address)) {
234345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                if (isValidAddress(address)) {
235345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                    String name = token.getName();
236345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                    if (TextUtils.isEmpty(name)) {
237345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                        name = null;
238345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                    }
239345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                    addresses.add(new Address(address, name));
240345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                }
241345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            }
242345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
243821e578a71c7015646522e729600618f0ec16fc0Tony Mantler        return addresses.toArray(new Address[addresses.size()]);
244345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
245345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
246345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
247345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Checks whether a string email address is valid.
248345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * E.g. name@domain.com is valid.
249345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
250345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    @VisibleForTesting
251f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu    static boolean isValidAddress(final String address) {
252f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu        return EMAIL_ADDRESS.matcher(address).find();
253345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
254345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
255345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    @Override
256345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public boolean equals(Object o) {
257345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (o instanceof Address) {
258345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            // It seems that the spec says that the "user" part is case-sensitive,
259345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            // while the domain part in case-insesitive.
260345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            // So foo@yahoo.com and Foo@yahoo.com are different.
261345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            // This may seem non-intuitive from the user POV, so we
262345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            // may re-consider it if it creates UI trouble.
263345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            // A problem case is "replyAll" sending to both
264345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            // a@b.c and to A@b.c, which turn out to be the same on the server.
265345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            // Leave unchanged for now (i.e. case-sensitive).
266345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return getAddress().equals(((Address) o).getAddress());
267345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
268345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        return super.equals(o);
269345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
270345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
271aa27bc0e1c3bb6be4609b00007637a9d3e960f5eScott Kennedy    @Override
272345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public int hashCode() {
273345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        return getAddress().hashCode();
274345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
275345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
276345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
277345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Get human readable address string.
278345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Do not use this for email header.
279345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     *
280345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @return Human readable address string.  Not quoted and not encoded.
281345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
282345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    @Override
283345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public String toString() {
284345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (mPersonal != null && !mPersonal.equals(mAddress)) {
285345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            if (mPersonal.matches(".*[\\(\\)<>@,;:\\\\\".\\[\\]].*")) {
286821e578a71c7015646522e729600618f0ec16fc0Tony Mantler                return ensureQuotedString(mPersonal) + " <" + mAddress + ">";
287345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            } else {
288345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                return mPersonal + " <" + mAddress + ">";
289345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            }
290345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        } else {
291345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return mAddress;
292345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
293345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
294345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
295345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
296345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Ensures that the given string starts and ends with the double quote character. The string is
297345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * not modified in any way except to add the double quote character to start and end if it's not
298345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * already there.
299345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     *
300345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * sample -> "sample"
301345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * "sample" -> "sample"
302345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * ""sample"" -> "sample"
303345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * "sample"" -> "sample"
304345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * sa"mp"le -> "sa"mp"le"
305345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * "sa"mp"le" -> "sa"mp"le"
306345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * (empty string) -> ""
307345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * " -> ""
308345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
309821e578a71c7015646522e729600618f0ec16fc0Tony Mantler    private static String ensureQuotedString(String s) {
310345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (s == null) {
311345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return null;
312345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
313345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (!s.matches("^\".*\"$")) {
314345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return "\"" + s + "\"";
315821e578a71c7015646522e729600618f0ec16fc0Tony Mantler        } else {
316345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return s;
317345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
318345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
319345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
320345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
321345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Get human readable comma-delimited address string.
322345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     *
323345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @param addresses Address array
324345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @return Human readable comma-delimited address string.
325345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
32623c95321afcccdfebcaff33f8be07a07b8c435d7Scott Kennedy    @VisibleForTesting
327345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public static String toString(Address[] addresses) {
328821e578a71c7015646522e729600618f0ec16fc0Tony Mantler        return toString(addresses, ADDRESS_DELIMETER);
329345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
330345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
331345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
332345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Get human readable address strings joined with the specified separator.
333345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     *
334345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @param addresses Address array
335345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @param separator Separator
336345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @return Human readable comma-delimited address string.
337345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
338345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public static String toString(Address[] addresses, String separator) {
339345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (addresses == null || addresses.length == 0) {
340345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return null;
341345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
342345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (addresses.length == 1) {
343345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return addresses[0].toString();
344345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
345821e578a71c7015646522e729600618f0ec16fc0Tony Mantler        StringBuilder sb = new StringBuilder(addresses[0].toString());
346345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        for (int i = 1; i < addresses.length; i++) {
347345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            sb.append(separator);
348345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            // TODO: investigate why this .trim() is needed.
349345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            sb.append(addresses[i].toString().trim());
350345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
351345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        return sb.toString();
352345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
353345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
354345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
355345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Get RFC822/MIME compatible address string.
356345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     *
357345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @return RFC822/MIME compatible address string.
358345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * It may be surrounded by double quote or quoted and MIME/base64 encoded if necessary.
359345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
360345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public String toHeader() {
361345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (mPersonal != null) {
362345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return EncoderUtil.encodeAddressDisplayName(mPersonal) + " <" + mAddress + ">";
363345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        } else {
364345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return mAddress;
365345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
366345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
367345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
368345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
369345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Get RFC822/MIME compatible comma-delimited address string.
370345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     *
371345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @param addresses Address array
372345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @return RFC822/MIME compatible comma-delimited address string.
373345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * it may be surrounded by double quoted or quoted and MIME/base64 encoded if necessary.
374345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
375345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public static String toHeader(Address[] addresses) {
376345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (addresses == null || addresses.length == 0) {
377345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return null;
378345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
379345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (addresses.length == 1) {
380345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return addresses[0].toHeader();
381345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
382821e578a71c7015646522e729600618f0ec16fc0Tony Mantler        StringBuilder sb = new StringBuilder(addresses[0].toHeader());
383345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        for (int i = 1; i < addresses.length; i++) {
384345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            // We need space character to be able to fold line.
385345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            sb.append(", ");
386345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            sb.append(addresses[i].toHeader());
387345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
388345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        return sb.toString();
389345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
390345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
391345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
392345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Get Human friendly address string.
393345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     *
394345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @return the personal part of this Address, or the address part if the
395345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * personal part is not available
396345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
39723c95321afcccdfebcaff33f8be07a07b8c435d7Scott Kennedy    @VisibleForTesting
398345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public String toFriendly() {
399345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (mPersonal != null && mPersonal.length() > 0) {
400345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return mPersonal;
401345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        } else {
402345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return mAddress;
403345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
404345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
405345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
406345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
407345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Creates a comma-delimited list of addresses in the "friendly" format (see toFriendly() for
408345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * details on the per-address conversion).
409345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     *
410345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @param addresses Array of Address[] values
411345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @return A comma-delimited string listing all of the addresses supplied.  Null if source
412345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * was null or empty.
413345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
4141fa009551845a6454ca2b31cfd20999b113e7890Scott Kennedy    @VisibleForTesting
415345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public static String toFriendly(Address[] addresses) {
416345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (addresses == null || addresses.length == 0) {
417345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return null;
418345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
419345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (addresses.length == 1) {
420345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return addresses[0].toFriendly();
421345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
422821e578a71c7015646522e729600618f0ec16fc0Tony Mantler        StringBuilder sb = new StringBuilder(addresses[0].toFriendly());
423345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        for (int i = 1; i < addresses.length; i++) {
424345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            sb.append(", ");
425345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            sb.append(addresses[i].toFriendly());
426345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
427345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        return sb.toString();
428345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
429345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
430345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
4314d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux     * Returns exactly the same result as Address.toString(Address.fromHeader(addressList)).
432345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
43323c95321afcccdfebcaff33f8be07a07b8c435d7Scott Kennedy    @VisibleForTesting
4344d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux    public static String fromHeaderToString(String addressList) {
4354d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux        return toString(fromHeader(addressList));
436345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
437345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
438345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
4394d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux     * Returns exactly the same result as Address.toHeader(Address.parse(addressList)).
440345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
44123c95321afcccdfebcaff33f8be07a07b8c435d7Scott Kennedy    @VisibleForTesting
4424d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux    public static String parseToHeader(String addressList) {
4434d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux        return Address.toHeader(Address.parse(addressList));
444345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
445345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
446345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
4474d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux     * Returns null if the addressList has 0 addresses, otherwise returns the first address.
4484d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux     * The same as Address.fromHeader(addressList)[0] for non-empty list.
449345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * This is an utility method that offers some performance optimization opportunities.
450345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
45123c95321afcccdfebcaff33f8be07a07b8c435d7Scott Kennedy    @VisibleForTesting
4524d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux    public static Address firstAddress(String addressList) {
4534d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux        Address[] array = fromHeader(addressList);
454345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        return array.length > 0 ? array[0] : null;
455345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
456345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
457345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
4584d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux     * This method exists to convert an address list formatted in a deprecated legacy format to the
4594d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux     * standard RFC822 header format. {@link #fromHeader(String)} is capable of reading the legacy
4604d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux     * format and the RFC822 format. {@link #toHeader()} always produces the RFC822 format.
4614d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux     *
462345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * This implementation is brute-force, and could be replaced with a more efficient version
463345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * if desired.
464345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
4654d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux    public static String reformatToHeader(String addressList) {
4664d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux        return toHeader(fromHeader(addressList));
467345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
468345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
469345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
4704d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux     * @param addressList a CSV of RFC822 addresses or the deprecated legacy string format
4714d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux     * @return array of addresses parsed from <code>addressList</code>
472345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
47323c95321afcccdfebcaff33f8be07a07b8c435d7Scott Kennedy    @VisibleForTesting
4744d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux    public static Address[] fromHeader(String addressList) {
475345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (addressList == null || addressList.length() == 0) {
476345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return EMPTY_ADDRESS_ARRAY;
477345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
478345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        // IF we're CSV, just parse
479345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if ((addressList.indexOf(LIST_DELIMITER_PERSONAL) == -1) &&
480345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                (addressList.indexOf(LIST_DELIMITER_EMAIL) == -1)) {
481345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return Address.parse(addressList);
482345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
483821e578a71c7015646522e729600618f0ec16fc0Tony Mantler        // Otherwise, do backward-compatible unpack
484345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        ArrayList<Address> addresses = new ArrayList<Address>();
485345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        int length = addressList.length();
486345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        int pairStartIndex = 0;
487821e578a71c7015646522e729600618f0ec16fc0Tony Mantler        int pairEndIndex;
488345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
489345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        /* addressEndIndex is only re-scanned (indexOf()) when a LIST_DELIMITER_PERSONAL
490345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein           is used, not for every email address; i.e. not for every iteration of the while().
491345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein           This reduces the theoretical complexity from quadratic to linear,
492345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein           and provides some speed-up in practice by removing redundant scans of the string.
493345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        */
494345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        int addressEndIndex = addressList.indexOf(LIST_DELIMITER_PERSONAL);
495345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
496345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        while (pairStartIndex < length) {
497345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            pairEndIndex = addressList.indexOf(LIST_DELIMITER_EMAIL, pairStartIndex);
498345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            if (pairEndIndex == -1) {
499345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                pairEndIndex = length;
500345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            }
501345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            Address address;
502345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            if (addressEndIndex == -1 || pairEndIndex <= addressEndIndex) {
503345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                // in this case the DELIMITER_PERSONAL is in a future pair,
504345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                // so don't use personal, and don't update addressEndIndex
505345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                address = new Address(addressList.substring(pairStartIndex, pairEndIndex), null);
506345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            } else {
507345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                address = new Address(addressList.substring(pairStartIndex, addressEndIndex),
508821e578a71c7015646522e729600618f0ec16fc0Tony Mantler                        addressList.substring(addressEndIndex + 1, pairEndIndex));
509345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                // only update addressEndIndex when we use the LIST_DELIMITER_PERSONAL
510345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                addressEndIndex = addressList.indexOf(LIST_DELIMITER_PERSONAL, pairEndIndex + 1);
511345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            }
512345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            addresses.add(address);
513345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            pairStartIndex = pairEndIndex + 1;
514345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
515821e578a71c7015646522e729600618f0ec16fc0Tony Mantler        return addresses.toArray(new Address[addresses.size()]);
516345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
517345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
518821e578a71c7015646522e729600618f0ec16fc0Tony Mantler    public static final Creator<Address> CREATOR = new Creator<Address>() {
519821e578a71c7015646522e729600618f0ec16fc0Tony Mantler        @Override
520821e578a71c7015646522e729600618f0ec16fc0Tony Mantler        public Address createFromParcel(Parcel parcel) {
521821e578a71c7015646522e729600618f0ec16fc0Tony Mantler            return new Address(parcel);
522345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
523821e578a71c7015646522e729600618f0ec16fc0Tony Mantler
524821e578a71c7015646522e729600618f0ec16fc0Tony Mantler        @Override
525821e578a71c7015646522e729600618f0ec16fc0Tony Mantler        public Address[] newArray(int size) {
526821e578a71c7015646522e729600618f0ec16fc0Tony Mantler            return new Address[size];
527821e578a71c7015646522e729600618f0ec16fc0Tony Mantler        }
528821e578a71c7015646522e729600618f0ec16fc0Tony Mantler    };
529821e578a71c7015646522e729600618f0ec16fc0Tony Mantler
530821e578a71c7015646522e729600618f0ec16fc0Tony Mantler    public Address(Parcel in) {
531821e578a71c7015646522e729600618f0ec16fc0Tony Mantler        setPersonal(in.readString());
532821e578a71c7015646522e729600618f0ec16fc0Tony Mantler        setAddress(in.readString());
533821e578a71c7015646522e729600618f0ec16fc0Tony Mantler    }
534821e578a71c7015646522e729600618f0ec16fc0Tony Mantler
535821e578a71c7015646522e729600618f0ec16fc0Tony Mantler    @Override
536821e578a71c7015646522e729600618f0ec16fc0Tony Mantler    public int describeContents() {
537821e578a71c7015646522e729600618f0ec16fc0Tony Mantler        return 0;
538821e578a71c7015646522e729600618f0ec16fc0Tony Mantler    }
539821e578a71c7015646522e729600618f0ec16fc0Tony Mantler
540821e578a71c7015646522e729600618f0ec16fc0Tony Mantler    @Override
541821e578a71c7015646522e729600618f0ec16fc0Tony Mantler    public void writeToParcel(Parcel out, int flags) {
542821e578a71c7015646522e729600618f0ec16fc0Tony Mantler        out.writeString(mPersonal);
543821e578a71c7015646522e729600618f0ec16fc0Tony Mantler        out.writeString(mAddress);
544345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
545345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein}
546