1 /****************************************************************
2 * Licensed to the Apache Software Foundation (ASF) under one *
3 * or more contributor license agreements. See the NOTICE file *
4 * distributed with this work for additional information *
5 * regarding copyright ownership. The ASF licenses this file *
6 * to you under the Apache License, Version 2.0 (the *
7 * "License"); you may not use this file except in compliance *
8 * with the License. You may obtain a copy of the License at *
9 * *
10 * http://www.apache.org/licenses/LICENSE-2.0 *
11 * *
12 * Unless required by applicable law or agreed to in writing, *
13 * software distributed under the License is distributed on an *
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
15 * KIND, either express or implied. See the License for the *
16 * specific language governing permissions and limitations *
17 * under the License. *
18 ****************************************************************/
19
20 package org.apache.james.mime4j.field.address;
21
22 import java.io.StringReader;
23 import java.util.Collections;
24 import java.util.List;
25 import java.util.Locale;
26
27 import org.apache.james.mime4j.codec.EncoderUtil;
28 import org.apache.james.mime4j.field.address.parser.AddressListParser;
29 import org.apache.james.mime4j.field.address.parser.ParseException;
30
31 /**
32 * Represents a single e-mail address.
33 */
34 public class Mailbox extends Address {
35
36 private static final long serialVersionUID = 1L;
37
38 private static final DomainList EMPTY_ROUTE_LIST = new DomainList(
39 Collections.<String> emptyList(), true);
40
41 private final String name;
42 private final DomainList route;
43 private final String localPart;
44 private final String domain;
45
46 /**
47 * Creates an unnamed mailbox without a route. Routes are obsolete.
48 *
49 * @param localPart
50 * The part of the e-mail address to the left of the "@".
51 * @param domain
52 * The part of the e-mail address to the right of the "@".
53 */
54 public Mailbox(String localPart, String domain) {
55 this(null, null, localPart, domain);
56 }
57
58 /**
59 * Creates an unnamed mailbox with a route. Routes are obsolete.
60 *
61 * @param route
62 * The zero or more domains that make up the route. May be
63 * <code>null</code>.
64 * @param localPart
65 * The part of the e-mail address to the left of the "@".
66 * @param domain
67 * The part of the e-mail address to the right of the "@".
68 */
69 public Mailbox(DomainList route, String localPart, String domain) {
70 this(null, route, localPart, domain);
71 }
72
73 /**
74 * Creates a named mailbox without a route. Routes are obsolete.
75 *
76 * @param name
77 * the name of the e-mail address. May be <code>null</code>.
78 * @param localPart
79 * The part of the e-mail address to the left of the "@".
80 * @param domain
81 * The part of the e-mail address to the right of the "@".
82 */
83 public Mailbox(String name, String localPart, String domain) {
84 this(name, null, localPart, domain);
85 }
86
87 /**
88 * Creates a named mailbox with a route. Routes are obsolete.
89 *
90 * @param name
91 * the name of the e-mail address. May be <code>null</code>.
92 * @param route
93 * The zero or more domains that make up the route. May be
94 * <code>null</code>.
95 * @param localPart
96 * The part of the e-mail address to the left of the "@".
97 * @param domain
98 * The part of the e-mail address to the right of the "@".
99 */
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 }