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 }