001    /****************************************************************
002     * Licensed to the Apache Software Foundation (ASF) under one   *
003     * or more contributor license agreements.  See the NOTICE file *
004     * distributed with this work for additional information        *
005     * regarding copyright ownership.  The ASF licenses this file   *
006     * to you under the Apache License, Version 2.0 (the            *
007     * "License"); you may not use this file except in compliance   *
008     * with the License.  You may obtain a copy of the License at   *
009     *                                                              *
010     *   http://www.apache.org/licenses/LICENSE-2.0                 *
011     *                                                              *
012     * Unless required by applicable law or agreed to in writing,   *
013     * software distributed under the License is distributed on an  *
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
015     * KIND, either express or implied.  See the License for the    *
016     * specific language governing permissions and limitations      *
017     * under the License.                                           *
018     ****************************************************************/
019    
020    package org.apache.james.mime4j.field.address;
021    
022    import java.io.StringReader;
023    import java.util.Collections;
024    import java.util.List;
025    import java.util.Locale;
026    
027    import org.apache.james.mime4j.codec.EncoderUtil;
028    import org.apache.james.mime4j.field.address.parser.AddressListParser;
029    import org.apache.james.mime4j.field.address.parser.ParseException;
030    
031    /**
032     * Represents a single e-mail address.
033     */
034    public class Mailbox extends Address {
035    
036        private static final long serialVersionUID = 1L;
037    
038        private static final DomainList EMPTY_ROUTE_LIST = new DomainList(
039                Collections.<String> emptyList(), true);
040    
041        private final String name;
042        private final DomainList route;
043        private final String localPart;
044        private final String domain;
045    
046        /**
047         * Creates an unnamed mailbox without a route. Routes are obsolete.
048         * 
049         * @param localPart
050         *            The part of the e-mail address to the left of the "@".
051         * @param domain
052         *            The part of the e-mail address to the right of the "@".
053         */
054        public Mailbox(String localPart, String domain) {
055            this(null, null, localPart, domain);
056        }
057    
058        /**
059         * Creates an unnamed mailbox with a route. Routes are obsolete.
060         * 
061         * @param route
062         *            The zero or more domains that make up the route. May be
063         *            <code>null</code>.
064         * @param localPart
065         *            The part of the e-mail address to the left of the "@".
066         * @param domain
067         *            The part of the e-mail address to the right of the "@".
068         */
069        public Mailbox(DomainList route, String localPart, String domain) {
070            this(null, route, localPart, domain);
071        }
072    
073        /**
074         * Creates a named mailbox without a route. Routes are obsolete.
075         * 
076         * @param name
077         *            the name of the e-mail address. May be <code>null</code>.
078         * @param localPart
079         *            The part of the e-mail address to the left of the "@".
080         * @param domain
081         *            The part of the e-mail address to the right of the "@".
082         */
083        public Mailbox(String name, String localPart, String domain) {
084            this(name, null, localPart, domain);
085        }
086    
087        /**
088         * Creates a named mailbox with a route. Routes are obsolete.
089         * 
090         * @param name
091         *            the name of the e-mail address. May be <code>null</code>.
092         * @param route
093         *            The zero or more domains that make up the route. May be
094         *            <code>null</code>.
095         * @param localPart
096         *            The part of the e-mail address to the left of the "@".
097         * @param domain
098         *            The part of the e-mail address to the right of the "@".
099         */
100        public Mailbox(String name, DomainList route, String localPart,
101                String domain) {
102            if (localPart == null || localPart.length() == 0)
103                throw new IllegalArgumentException();
104    
105            this.name = name == null || name.length() == 0 ? null : name;
106            this.route = route == null ? EMPTY_ROUTE_LIST : route;
107            this.localPart = localPart;
108            this.domain = domain == null || domain.length() == 0 ? null : domain;
109        }
110    
111        /**
112         * Creates a named mailbox based on an unnamed mailbox. Package private;
113         * internally used by Builder.
114         */
115        Mailbox(String name, Mailbox baseMailbox) {
116            this(name, baseMailbox.getRoute(), baseMailbox.getLocalPart(),
117                    baseMailbox.getDomain());
118        }
119    
120        /**
121         * Parses the specified raw string into a mailbox address.
122         * 
123         * @param rawMailboxString
124         *            string to parse.
125         * @return a <code>Mailbox</code> object for the specified string.
126         * @throws IllegalArgumentException
127         *             if the raw string does not represent a single mailbox
128         *             address.
129         */
130        public static Mailbox parse(String rawMailboxString) {
131            AddressListParser parser = new AddressListParser(new StringReader(
132                    rawMailboxString));
133            try {
134                return Builder.getInstance().buildMailbox(parser.parseMailbox());
135            } catch (ParseException e) {
136                throw new IllegalArgumentException(e);
137            }
138        }
139    
140        /**
141         * Returns the name of the mailbox or <code>null</code> if it does not
142         * have a name.
143         */
144        public String getName() {
145            return name;
146        }
147    
148        /**
149         * Returns the route list. If the mailbox does not have a route an empty
150         * domain list is returned.
151         */
152        public DomainList getRoute() {
153            return route;
154        }
155    
156        /**
157         * Returns the left part of the e-mail address (before "@").
158         */
159        public String getLocalPart() {
160            return localPart;
161        }
162    
163        /**
164         * Returns the right part of the e-mail address (after "@").
165         */
166        public String getDomain() {
167            return domain;
168        }
169    
170        /**
171         * Returns the address in the form <i>localPart@domain</i>.
172         * 
173         * @return the address part of this mailbox.
174         */
175        public String getAddress() {
176            if (domain == null) {
177                return localPart;
178            } else {
179                return localPart + '@' + domain;
180            }
181        }
182    
183        @Override
184        public String getDisplayString(boolean includeRoute) {
185            includeRoute &= route != null;
186            boolean includeAngleBrackets = name != null || includeRoute;
187    
188            StringBuilder sb = new StringBuilder();
189    
190            if (name != null) {
191                sb.append(name);
192                sb.append(' ');
193            }
194    
195            if (includeAngleBrackets) {
196                sb.append('<');
197            }
198    
199            if (includeRoute) {
200                sb.append(route.toRouteString());
201                sb.append(':');
202            }
203    
204            sb.append(localPart);
205    
206            if (domain != null) {
207                sb.append('@');
208                sb.append(domain);
209            }
210    
211            if (includeAngleBrackets) {
212                sb.append('>');
213            }
214    
215            return sb.toString();
216        }
217    
218        @Override
219        public String getEncodedString() {
220            StringBuilder sb = new StringBuilder();
221    
222            if (name != null) {
223                sb.append(EncoderUtil.encodeAddressDisplayName(name));
224                sb.append(" <");
225            }
226    
227            sb.append(EncoderUtil.encodeAddressLocalPart(localPart));
228    
229            // domain = dot-atom / domain-literal
230            // domain-literal = [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS]
231            // dtext = %d33-90 / %d94-126
232            if (domain != null) {
233                sb.append('@');
234                sb.append(domain);
235            }
236    
237            if (name != null) {
238                sb.append('>');
239            }
240    
241            return sb.toString();
242        }
243    
244        @Override
245        public int hashCode() {
246            return getCanonicalizedAddress().hashCode();
247        }
248    
249        /**
250         * Indicates whether some other object is "equal to" this mailbox.
251         * <p>
252         * An object is considered to be equal to this mailbox if it is an instance
253         * of class <code>Mailbox</code> that holds the same address as this one.
254         * The domain is considered to be case-insensitive but the local-part is not
255         * (because of RFC 5321: <cite>the local-part of a mailbox MUST BE treated
256         * as case sensitive</cite>).
257         * 
258         * @param obj
259         *            the object to test for equality.
260         * @return <code>true</code> if the specified object is a
261         *         <code>Mailbox</code> that holds the same address as this one.
262         */
263        @Override
264        public boolean equals(Object obj) {
265            if (obj == this)
266                return true;
267            if (!(obj instanceof Mailbox))
268                return false;
269    
270            Mailbox other = (Mailbox) obj;
271            return getCanonicalizedAddress()
272                    .equals(other.getCanonicalizedAddress());
273        }
274    
275        @Override
276        protected final void doAddMailboxesTo(List<Mailbox> results) {
277            results.add(this);
278        }
279    
280        private Object getCanonicalizedAddress() {
281            if (domain == null) {
282                return localPart;
283            } else {
284                return localPart + '@' + domain.toLowerCase(Locale.US);
285            }
286        }
287    
288    }