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 Sapperstein
17345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinpackage com.android.emailcommon.mail;
18345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
19345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport android.text.TextUtils;
20345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport android.text.util.Rfc822Token;
21345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport android.text.util.Rfc822Tokenizer;
22345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
23345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport com.google.common.annotations.VisibleForTesting;
24345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
25345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport org.apache.james.mime4j.codec.EncoderUtil;
26345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport org.apache.james.mime4j.decoder.DecoderUtil;
27345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
28345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport java.util.ArrayList;
29345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport java.util.regex.Pattern;
30345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
31345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein/**
32345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * This class represent email address.
33345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein *
34345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * RFC822 email address may have following format.
35345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein *   "name" <address> (comment)
36345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein *   "name" <address>
37345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein *   name <address>
38345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein *   address
39345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Name and comment part should be MIME/base64 encoded in header if necessary.
40345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein *
41345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */
42345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinpublic class Address {
43345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
44345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     *  Address part, in the form local_part@domain_part. No surrounding angle brackets.
45345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
46345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    private String mAddress;
47345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
48345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
49345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Name part. No surrounding double quote, and no MIME/base64 encoding.
50345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * This must be null if Address has no name part.
51345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
52345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    private String mPersonal;
53345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
54345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    // Regex that matches address surrounded by '<>' optionally. '^<?([^>]+)>?$'
55345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    private static final Pattern REMOVE_OPTIONAL_BRACKET = Pattern.compile("^<?([^>]+)>?$");
56345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    // Regex that matches personal name surrounded by '""' optionally. '^"?([^"]+)"?$'
57345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    private static final Pattern REMOVE_OPTIONAL_DQUOTE = Pattern.compile("^\"?([^\"]*)\"?$");
58345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    // Regex that matches escaped character '\\([\\"])'
59345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    private static final Pattern UNQUOTE = Pattern.compile("\\\\([\\\\\"])");
60345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
61f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu
62f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu    // TODO: LOCAL_PART and DOMAIN_PART_PART are too permissive and can be improved.
63f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu    // TODO: Fix this to better constrain comments.
64f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu    /** Regex for the local part of an email address. */
65f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu    private static final String LOCAL_PART = "[^@]+";
66f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu    /** Regex for each part of the domain part, i.e. the thing between the dots. */
67f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu    private static final String DOMAIN_PART_PART = "[[\\w][\\d]\\-\\(\\)\\[\\]]+";
68f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu    /** Regex for the domain part, which is two or more {@link #DOMAIN_PART_PART} separated by . */
69f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu    private static final String DOMAIN_PART =
70f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu            "(" + DOMAIN_PART_PART + "\\.)+" + DOMAIN_PART_PART;
71f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu
72f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu    /** Pattern to check if an email address is valid. */
73f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu    private static final Pattern EMAIL_ADDRESS =
74f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu            Pattern.compile("\\A" + LOCAL_PART + "@" + DOMAIN_PART + "\\z");
75f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu
76345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    private static final Address[] EMPTY_ADDRESS_ARRAY = new Address[0];
77345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
78345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    // delimiters are chars that do not appear in an email address, used by pack/unpack
79345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    private static final char LIST_DELIMITER_EMAIL = '\1';
80345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    private static final char LIST_DELIMITER_PERSONAL = '\2';
81345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
82345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public Address(String address, String personal) {
83345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        setAddress(address);
84345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        setPersonal(personal);
85345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
86345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
87345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public Address(String address) {
88345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        setAddress(address);
89345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
90345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
91345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public String getAddress() {
92345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        return mAddress;
93345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
94345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
95345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public void setAddress(String address) {
96345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        mAddress = REMOVE_OPTIONAL_BRACKET.matcher(address).replaceAll("$1");
97345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
98345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
99345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
100345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Get name part as UTF-16 string. No surrounding double quote, and no MIME/base64 encoding.
101345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     *
102345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @return Name part of email address. Returns null if it is omitted.
103345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
104345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public String getPersonal() {
105345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        return mPersonal;
106345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
107345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
108345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
109345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Set name part from UTF-16 string. Optional surrounding double quote will be removed.
110345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * It will be also unquoted and MIME/base64 decoded.
111345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     *
112345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @param personal name part of email address as UTF-16 string. Null is acceptable.
113345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
114345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public void setPersonal(String personal) {
115345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (personal != null) {
116345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            personal = REMOVE_OPTIONAL_DQUOTE.matcher(personal).replaceAll("$1");
117345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            personal = UNQUOTE.matcher(personal).replaceAll("$1");
118345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            personal = DecoderUtil.decodeEncodedWords(personal);
119345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            if (personal.length() == 0) {
120345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                personal = null;
121345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            }
122345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
123345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        mPersonal = personal;
124345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
125345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
126345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
127345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * This method is used to check that all the addresses that the user
128345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * entered in a list (e.g. To:) are valid, so that none is dropped.
129345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
130345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public static boolean isAllValid(String addressList) {
131345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        // This code mimics the parse() method below.
132345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        // I don't know how to better avoid the code-duplication.
133345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (addressList != null && addressList.length() > 0) {
134345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(addressList);
135345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            for (int i = 0, length = tokens.length; i < length; ++i) {
136345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                Rfc822Token token = tokens[i];
137345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                String address = token.getAddress();
138345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                if (!TextUtils.isEmpty(address) && !isValidAddress(address)) {
139345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                    return false;
140345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                }
141345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            }
142345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
143345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        return true;
144345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
145345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
146345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
147345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Parse a comma-delimited list of addresses in RFC822 format and return an
148345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * array of Address objects.
149345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     *
150345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @param addressList Address list in comma-delimited string.
151345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @return An array of 0 or more Addresses.
152345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
153345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public static Address[] parse(String addressList) {
154345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (addressList == null || addressList.length() == 0) {
155345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return EMPTY_ADDRESS_ARRAY;
156345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
157345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(addressList);
158345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        ArrayList<Address> addresses = new ArrayList<Address>();
159345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        for (int i = 0, length = tokens.length; i < length; ++i) {
160345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            Rfc822Token token = tokens[i];
161345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            String address = token.getAddress();
162345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            if (!TextUtils.isEmpty(address)) {
163345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                if (isValidAddress(address)) {
164345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                    String name = token.getName();
165345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                    if (TextUtils.isEmpty(name)) {
166345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                        name = null;
167345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                    }
168345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                    addresses.add(new Address(address, name));
169345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                }
170345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            }
171345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
172345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        return addresses.toArray(new Address[] {});
173345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
174345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
175345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
176345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Checks whether a string email address is valid.
177345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * E.g. name@domain.com is valid.
178345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
179345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    @VisibleForTesting
180f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu    static boolean isValidAddress(final String address) {
181f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu        return EMAIL_ADDRESS.matcher(address).find();
182345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
183345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
184345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    @Override
185345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public boolean equals(Object o) {
186345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (o instanceof Address) {
187345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            // It seems that the spec says that the "user" part is case-sensitive,
188345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            // while the domain part in case-insesitive.
189345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            // So foo@yahoo.com and Foo@yahoo.com are different.
190345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            // This may seem non-intuitive from the user POV, so we
191345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            // may re-consider it if it creates UI trouble.
192345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            // A problem case is "replyAll" sending to both
193345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            // a@b.c and to A@b.c, which turn out to be the same on the server.
194345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            // Leave unchanged for now (i.e. case-sensitive).
195345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return getAddress().equals(((Address) o).getAddress());
196345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
197345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        return super.equals(o);
198345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
199345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
200aa27bc0e1c3bb6be4609b00007637a9d3e960f5eScott Kennedy    @Override
201345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public int hashCode() {
202345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        return getAddress().hashCode();
203345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
204345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
205345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
206345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Get human readable address string.
207345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Do not use this for email header.
208345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     *
209345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @return Human readable address string.  Not quoted and not encoded.
210345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
211345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    @Override
212345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public String toString() {
213345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (mPersonal != null && !mPersonal.equals(mAddress)) {
214345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            if (mPersonal.matches(".*[\\(\\)<>@,;:\\\\\".\\[\\]].*")) {
215345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                return quoteString(mPersonal) + " <" + mAddress + ">";
216345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            } else {
217345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                return mPersonal + " <" + mAddress + ">";
218345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            }
219345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        } else {
220345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return mAddress;
221345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
222345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
223345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
224345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
225345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Ensures that the given string starts and ends with the double quote character. The string is
226345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * not modified in any way except to add the double quote character to start and end if it's not
227345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * already there.
228345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     *
229345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * TODO: Rename this, because "quoteString()" can mean so many different things.
230345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     *
231345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * sample -> "sample"
232345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * "sample" -> "sample"
233345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * ""sample"" -> "sample"
234345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * "sample"" -> "sample"
235345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * sa"mp"le -> "sa"mp"le"
236345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * "sa"mp"le" -> "sa"mp"le"
237345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * (empty string) -> ""
238345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * " -> ""
239345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
240345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public static String quoteString(String s) {
241345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (s == null) {
242345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return null;
243345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
244345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (!s.matches("^\".*\"$")) {
245345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return "\"" + s + "\"";
246345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
247345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        else {
248345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return s;
249345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
250345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
251345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
252345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
253345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Get human readable comma-delimited address string.
254345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     *
255345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @param addresses Address array
256345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @return Human readable comma-delimited address string.
257345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
258345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public static String toString(Address[] addresses) {
259345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        return toString(addresses, ",");
260345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
261345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
262345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
263345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Get human readable address strings joined with the specified separator.
264345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     *
265345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @param addresses Address array
266345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @param separator Separator
267345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @return Human readable comma-delimited address string.
268345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
269345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public static String toString(Address[] addresses, String separator) {
270345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (addresses == null || addresses.length == 0) {
271345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return null;
272345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
273345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (addresses.length == 1) {
274345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return addresses[0].toString();
275345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
276345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        StringBuffer sb = new StringBuffer(addresses[0].toString());
277345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        for (int i = 1; i < addresses.length; i++) {
278345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            sb.append(separator);
279345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            // TODO: investigate why this .trim() is needed.
280345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            sb.append(addresses[i].toString().trim());
281345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
282345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        return sb.toString();
283345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
284345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
285345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
286345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Get RFC822/MIME compatible address string.
287345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     *
288345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @return RFC822/MIME compatible address string.
289345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * It may be surrounded by double quote or quoted and MIME/base64 encoded if necessary.
290345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
291345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public String toHeader() {
292345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (mPersonal != null) {
293345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return EncoderUtil.encodeAddressDisplayName(mPersonal) + " <" + mAddress + ">";
294345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        } else {
295345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return mAddress;
296345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
297345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
298345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
299345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
300345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Get RFC822/MIME compatible comma-delimited address string.
301345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     *
302345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @param addresses Address array
303345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @return RFC822/MIME compatible comma-delimited address string.
304345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * it may be surrounded by double quoted or quoted and MIME/base64 encoded if necessary.
305345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
306345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public static String toHeader(Address[] addresses) {
307345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (addresses == null || addresses.length == 0) {
308345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return null;
309345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
310345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (addresses.length == 1) {
311345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return addresses[0].toHeader();
312345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
313345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        StringBuffer sb = new StringBuffer(addresses[0].toHeader());
314345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        for (int i = 1; i < addresses.length; i++) {
315345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            // We need space character to be able to fold line.
316345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            sb.append(", ");
317345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            sb.append(addresses[i].toHeader());
318345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
319345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        return sb.toString();
320345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
321345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
322345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
323345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Get Human friendly address string.
324345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     *
325345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @return the personal part of this Address, or the address part if the
326345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * personal part is not available
327345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
328345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public String toFriendly() {
329345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (mPersonal != null && mPersonal.length() > 0) {
330345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return mPersonal;
331345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        } else {
332345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return mAddress;
333345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
334345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
335345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
336345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
337345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Creates a comma-delimited list of addresses in the "friendly" format (see toFriendly() for
338345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * details on the per-address conversion).
339345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     *
340345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @param addresses Array of Address[] values
341345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @return A comma-delimited string listing all of the addresses supplied.  Null if source
342345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * was null or empty.
343345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
344345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public static String toFriendly(Address[] addresses) {
345345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (addresses == null || addresses.length == 0) {
346345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return null;
347345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
348345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (addresses.length == 1) {
349345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return addresses[0].toFriendly();
350345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
351345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        StringBuffer sb = new StringBuffer(addresses[0].toFriendly());
352345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        for (int i = 1; i < addresses.length; i++) {
353345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            sb.append(", ");
354345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            sb.append(addresses[i].toFriendly());
355345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
356345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        return sb.toString();
357345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
358345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
359345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
360345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Returns exactly the same result as Address.toString(Address.unpack(packedList)).
361345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
362345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public static String unpackToString(String packedList) {
363345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        return toString(unpack(packedList));
364345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
365345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
366345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
367345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Returns exactly the same result as Address.pack(Address.parse(textList)).
368345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
369345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public static String parseAndPack(String textList) {
370345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        return Address.pack(Address.parse(textList));
371345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
372345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
373345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
374345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Returns null if the packedList has 0 addresses, otherwise returns the first address.
375345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * The same as Address.unpack(packedList)[0] for non-empty list.
376345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * This is an utility method that offers some performance optimization opportunities.
377345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
378345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public static Address unpackFirst(String packedList) {
379345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        Address[] array = unpack(packedList);
380345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        return array.length > 0 ? array[0] : null;
381345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
382345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
383345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
384345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Convert a packed list of addresses to a form suitable for use in an RFC822 header.
385345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * This implementation is brute-force, and could be replaced with a more efficient version
386345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * if desired.
387345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
388345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public static String packedToHeader(String packedList) {
389345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        return toHeader(unpack(packedList));
390345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
391345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
392345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
393345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Unpacks an address list that is either CSV of RFC822 addresses OR (for backward
394345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * compatibility) previously packed with pack()
395345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @param addressList string packed with pack() or CSV of RFC822 addresses
396345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * @return array of addresses resulting from unpack
397345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
398345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public static Address[] unpack(String addressList) {
399345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (addressList == null || addressList.length() == 0) {
400345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return EMPTY_ADDRESS_ARRAY;
401345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
402345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        // IF we're CSV, just parse
403345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if ((addressList.indexOf(LIST_DELIMITER_PERSONAL) == -1) &&
404345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                (addressList.indexOf(LIST_DELIMITER_EMAIL) == -1)) {
405345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return Address.parse(addressList);
406345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
407345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        // Otherwise, do backward-compatibile unpack
408345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        ArrayList<Address> addresses = new ArrayList<Address>();
409345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        int length = addressList.length();
410345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        int pairStartIndex = 0;
411345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        int pairEndIndex = 0;
412345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
413345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        /* addressEndIndex is only re-scanned (indexOf()) when a LIST_DELIMITER_PERSONAL
414345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein           is used, not for every email address; i.e. not for every iteration of the while().
415345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein           This reduces the theoretical complexity from quadratic to linear,
416345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein           and provides some speed-up in practice by removing redundant scans of the string.
417345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        */
418345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        int addressEndIndex = addressList.indexOf(LIST_DELIMITER_PERSONAL);
419345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
420345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        while (pairStartIndex < length) {
421345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            pairEndIndex = addressList.indexOf(LIST_DELIMITER_EMAIL, pairStartIndex);
422345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            if (pairEndIndex == -1) {
423345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                pairEndIndex = length;
424345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            }
425345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            Address address;
426345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            if (addressEndIndex == -1 || pairEndIndex <= addressEndIndex) {
427345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                // in this case the DELIMITER_PERSONAL is in a future pair,
428345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                // so don't use personal, and don't update addressEndIndex
429345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                address = new Address(addressList.substring(pairStartIndex, pairEndIndex), null);
430345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            } else {
431345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                address = new Address(addressList.substring(pairStartIndex, addressEndIndex),
432345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                                      addressList.substring(addressEndIndex + 1, pairEndIndex));
433345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                // only update addressEndIndex when we use the LIST_DELIMITER_PERSONAL
434345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein                addressEndIndex = addressList.indexOf(LIST_DELIMITER_PERSONAL, pairEndIndex + 1);
435345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            }
436345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            addresses.add(address);
437345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            pairStartIndex = pairEndIndex + 1;
438345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
439345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        return addresses.toArray(EMPTY_ADDRESS_ARRAY);
440345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
441345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
442345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
443345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Generate a String containing RFC822 addresses separated by commas
444345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * NOTE: We used to "pack" these addresses in an app-specific format, but no longer do so
445345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
446345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public static String pack(Address[] addresses) {
447345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        return Address.toHeader(addresses);
448345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
449345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein
450345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    /**
451345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     * Produces the same result as pack(array), but only packs one (this) address.
452345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein     */
453345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    public String pack() {
454345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        final String address = getAddress();
455345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        final String personal = getPersonal();
456345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        if (personal == null) {
457345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return address;
458345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        } else {
459345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein            return address + LIST_DELIMITER_PERSONAL + personal;
460345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein        }
461345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein    }
462345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein}
463