| Line | Hits | Note | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * $Id: EmailAddress.java 1404 2009-04-14 12:34:34Z amandel $ | ||
| 3 | * | ||
| 4 | * Copyright 2006, The jCoderZ.org Project. All rights reserved. | ||
| 5 | * | ||
| 6 | * Redistribution and use in source and binary forms, with or without | ||
| 7 | * modification, are permitted provided that the following conditions are | ||
| 8 | * met: | ||
| 9 | * | ||
| 10 | * * Redistributions of source code must retain the above copyright | ||
| 11 | * notice, this list of conditions and the following disclaimer. | ||
| 12 | * * Redistributions in binary form must reproduce the above | ||
| 13 | * copyright notice, this list of conditions and the following | ||
| 14 | * disclaimer in the documentation and/or other materials | ||
| 15 | * provided with the distribution. | ||
| 16 | * * Neither the name of the jCoderZ.org Project nor the names of | ||
| 17 | * its contributors may be used to endorse or promote products | ||
| 18 | * derived from this software without specific prior written | ||
| 19 | * permission. | ||
| 20 | * | ||
| 21 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND | ||
| 22 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
| 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | ||
| 24 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS | ||
| 25 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | ||
| 26 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | ||
| 27 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR | ||
| 28 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, | ||
| 29 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR | ||
| 30 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | ||
| 31 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
| 32 | */ | ||
| 33 | package org.jcoderz.commons.types; | ||
| 34 | |||
| 35 | import java.io.Serializable; | ||
| 36 | import java.util.regex.Matcher; | ||
| 37 | import java.util.regex.Pattern; | ||
| 38 | |||
| 39 | import org.jcoderz.commons.ArgumentMalformedException; | ||
| 40 | import org.jcoderz.commons.util.Assert; | ||
| 41 | import org.jcoderz.commons.util.HashCodeUtil; | ||
| 42 | import org.jcoderz.commons.util.StringUtil; | ||
| 43 | |||
| 44 | /** | ||
| 45 | * This class represents an email addresses compliant to RFC 2822, | ||
| 46 | * chapter 3.4.1. Addr-spec specification. | ||
| 47 | * This class does not support obsolete addressing (see ch. 4.4. | ||
| 48 | * Obsolete Addressing). | ||
| 49 | * | ||
| 50 | * <pre> | ||
| 51 | * atext = ALPHA / DIGIT / ; Any character except controls, | ||
| 52 | * "!" / "#" / ; SP, and specials. | ||
| 53 | * "$" / "%" / ; Used for atoms | ||
| 54 | * "&" / "'" / | ||
| 55 | * "*" / "+" / | ||
| 56 | * "-" / "/" / | ||
| 57 | * "=" / "?" / | ||
| 58 | * "^" / "_" / | ||
| 59 | * "`" / "{" / | ||
| 60 | * "|" / "}" / | ||
| 61 | * "~" | ||
| 62 | * atom = [CFWS] 1*atext [CFWS] | ||
| 63 | * dot-atom = [CFWS] dot-atom-text [CFWS] | ||
| 64 | * dot-atom-text = 1*atext *("." 1*atext) | ||
| 65 | * addr-spec = local-part "@" domain | ||
| 66 | * local-part = dot-atom / quoted-string | ||
| 67 | * qtext = NO-WS-CTL / ; Non white space controls | ||
| 68 | * %d33 / ; The rest of the US-ASCII | ||
| 69 | * %d35-91 / ; characters not including "\" | ||
| 70 | * %d93-126 ; or the quote character | ||
| 71 | * qcontent = qtext / quoted-pair | ||
| 72 | * quoted-string = [CFWS] | ||
| 73 | * DQUOTE *([FWS] qcontent) [FWS] DQUOTE | ||
| 74 | * [CFWS] | ||
| 75 | * domain = dot-atom / domain-literal | ||
| 76 | * domain-literal = [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS] | ||
| 77 | * dcontent = dtext / quoted-pair | ||
| 78 | * dtext = NO-WS-CTL / ; Non white space controls | ||
| 79 | * %d33-90 / ; The rest of the US-ASCII | ||
| 80 | * %d94-126 ; characters not including "[", | ||
| 81 | * ; "]", or "\" | ||
| 82 | * FWS = ([*WSP CRLF] 1*WSP) / ; Folding white space | ||
| 83 | * obs-FWS | ||
| 84 | * ctext = NO-WS-CTL / ; Non white space controls | ||
| 85 | * %d33-39 / ; The rest of the US-ASCII | ||
| 86 | * %d42-91 / ; characters not including "(", | ||
| 87 | * %d93-126 ; ")", or "\" | ||
| 88 | * ccontent = ctext / quoted-pair / comment | ||
| 89 | * comment = "(" *([FWS] ccontent) [FWS] ")" | ||
| 90 | * CFWS = *([FWS] comment) (([FWS] comment) / FWS) | ||
| 91 | * </pre> | ||
| 92 | * | ||
| 93 | * <p>The maximum length of an email address is: | ||
| 94 | * 64+1+255 characters (local-part + @ + domain). | ||
| 95 | * The minimum length of an email address is: | ||
| 96 | * 1+1+4 characters (local-part + @ + domain).</p> | ||
| 97 | * | ||
| 98 | * <p>A valid list of top-level domains is defined by the IANA. A top-level | ||
| 99 | * domain which is not part of the | ||
| 100 | * <a href="http://data.iana.org/TLD/tlds-alpha-by-domain.txt">official list</a> | ||
| 101 | * will be rejected.</p> | ||
| 102 | * | ||
| 103 | * @author Michael Rumpf | ||
| 104 | */ | ||
| 105 | public class EmailAddress | ||
| 106 | implements Serializable | ||
| 107 | { | ||
| 108 | private static final long serialVersionUID = 1L; | ||
| 109 | |||
| 110 | private static final int MAX_LENGTH_LOCAL_PART = 64; | ||
| 111 | private static final int MAX_LENGTH_DOMAIN = 255; | ||
| 112 | |||
| 113 | // RFC 2822 token definitions for a valid email | ||
| 114 | private static final String SP = "!#$%&'*+-/=?^_`{|}~"; | ||
| 115 | private static final String ATEXT = "[a-zA-Z0-9" + SP + "]"; | ||
| 116 | private static final String ATOM = ATEXT + "+"; | ||
| 117 | |||
| 118 | // one or more atext chars | ||
| 119 | private static final String DOT_ATOM = "\\." + ATOM; | ||
| 120 | // one atom followed by 0 or more dotAtoms. | ||
| 121 | private static final String LOCAL_PART = ATOM + "(" + DOT_ATOM + ")*"; | ||
| 122 | |||
| 123 | // RFC 1035 tokens for domain names: | ||
| 124 | private static final String LETTER = "[a-zA-Z]"; | ||
| 125 | private static final String LET_DIG = "[a-zA-Z0-9]"; | ||
| 126 | private static final String LET_DIG_HYP = "[a-zA-Z0-9-]"; | ||
| 127 | private static final String RFC_LABEL | ||
| 128 | = LET_DIG + LET_DIG_HYP + "{0,61}" + LET_DIG; | ||
| 129 | private static final String DOMAIN | ||
| 130 | = RFC_LABEL + "(\\." + RFC_LABEL + ")*\\." + LETTER + "{2,6}"; | ||
| 131 | |||
| 132 | //Combined together, these form the allowed email regexp allowed by RFC 2822: | ||
| 133 | private static final String ADDRESS = "^" + LOCAL_PART + "@" + DOMAIN + "$"; | ||
| 134 | |||
| 135 | 100 | private static final Pattern ADDRESS_PATTERN = Pattern.compile(ADDRESS); | |
| 136 | |||
| 137 | |||
| 138 | private final String mLocalPart; | ||
| 139 | private final String mDomain; | ||
| 140 | private final String mTopLevelDomain; | ||
| 141 | |||
| 142 | /** | ||
| 143 | * This is the constructor of the EmailAddress type. | ||
| 144 | * | ||
| 145 | * @param email The email address. | ||
| 146 | */ | ||
| 147 | public EmailAddress (String email) | ||
| 148 | 100 | { | |
| 149 | 100 | (1) | Assert.notNull(email, "email"); |
| 150 | 100 | final String mail = email.trim(); | |
| 151 | 100 | final Matcher matcher = ADDRESS_PATTERN.matcher(email); | |
| 152 | 100 | if (!matcher.matches()) | |
| 153 | { | ||
| 154 | 100 | (2) | throw new ArgumentMalformedException("email", email, |
| 155 | "EMail pattern does not match the RFC2822 grammar!"); | ||
| 156 | } | ||
| 157 | |||
| 158 | 100 | final int at = mail.indexOf('@'); | |
| 159 | 100 | if (at > MAX_LENGTH_LOCAL_PART) | |
| 160 | { | ||
| 161 | 0 | (3) | throw new ArgumentMalformedException("email", email, |
| 162 | "The local part is longer than " + MAX_LENGTH_LOCAL_PART | ||
| 163 | + " characters!"); | ||
| 164 | } | ||
| 165 | 100 | if (mail.length() - at - 1 > MAX_LENGTH_DOMAIN) | |
| 166 | { | ||
| 167 | 0 | (4) | throw new ArgumentMalformedException("email", email, |
| 168 | "The domain is longer than " + MAX_LENGTH_DOMAIN | ||
| 169 | + " characters!"); | ||
| 170 | } | ||
| 171 | 100 | mLocalPart = mail.substring(0, at); | |
| 172 | 100 | mDomain = mail.substring(at + 1); | |
| 173 | |||
| 174 | 100 | final int dot = mail.lastIndexOf('.'); | |
| 175 | 100 | mTopLevelDomain = mail.substring(dot + 1); | |
| 176 | 100 | } | |
| 177 | |||
| 178 | /** | ||
| 179 | * Factory method for converting a String into an instance of type | ||
| 180 | * EmailAddress. | ||
| 181 | * | ||
| 182 | * @param email the email address to parse | ||
| 183 | * @return An instance of type EmailAddress | ||
| 184 | */ | ||
| 185 | (5) | public static EmailAddress fromString(String email) | |
| 186 | { | ||
| 187 | 0 | return new EmailAddress(email); | |
| 188 | } | ||
| 189 | |||
| 190 | /** | ||
| 191 | * Returns the local part of the address. | ||
| 192 | * @return the local part of the address. | ||
| 193 | */ | ||
| 194 | public String getName () | ||
| 195 | { | ||
| 196 | 100 | return mLocalPart; | |
| 197 | } | ||
| 198 | |||
| 199 | |||
| 200 | /** | ||
| 201 | * Returns the domain name of the address. | ||
| 202 | * @return the domain name of the address. | ||
| 203 | */ | ||
| 204 | public String getDomain () | ||
| 205 | { | ||
| 206 | 100 | return mDomain; | |
| 207 | } | ||
| 208 | |||
| 209 | |||
| 210 | /** | ||
| 211 | * Returns the top-level domain name of the address. | ||
| 212 | * @return the top-level domain name of the address. | ||
| 213 | */ | ||
| 214 | public String getTopLevelDomain () | ||
| 215 | { | ||
| 216 | 0 | return mTopLevelDomain; | |
| 217 | } | ||
| 218 | |||
| 219 | |||
| 220 | /** | ||
| 221 | * Returns the full email address. | ||
| 222 | * @return the full email address. | ||
| 223 | */ | ||
| 224 | public String getAddress () | ||
| 225 | { | ||
| 226 | 0 | return mLocalPart + "@" + mDomain; | |
| 227 | } | ||
| 228 | |||
| 229 | /** | ||
| 230 | * Returns the full email address. | ||
| 231 | * @return the full email address. | ||
| 232 | */ | ||
| 233 | public String toString () | ||
| 234 | { | ||
| 235 | 0 | return mLocalPart + "@" + mDomain; | |
| 236 | } | ||
| 237 | |||
| 238 | /** | ||
| 239 | * Returns the true if this email address equals the other. | ||
| 240 | * @param obj the object to compare to. | ||
| 241 | * @return the true if this email address equals the other. | ||
| 242 | * @see java.lang.Object#equals(java.lang.Object) | ||
| 243 | */ | ||
| 244 | public boolean equals (Object obj) | ||
| 245 | { | ||
| 246 | 0 | boolean result = false; | |
| 247 | 0 | if (this == obj) | |
| 248 | { | ||
| 249 | 0 | result = true; | |
| 250 | } | ||
| 251 | 0 | else if (obj instanceof EmailAddress) | |
| 252 | { | ||
| 253 | 0 | final EmailAddress other = (EmailAddress) obj; | |
| 254 | 0 | if (StringUtil.equals(getDomain(), other.getDomain()) | |
| 255 | && StringUtil.equals(getName(), other.getName())) | ||
| 256 | { | ||
| 257 | 0 | result = true; | |
| 258 | } | ||
| 259 | } | ||
| 260 | 0 | return result; | |
| 261 | } | ||
| 262 | |||
| 263 | /** | ||
| 264 | * Returns the hash code for this email. | ||
| 265 | * @return the hash code for this email. | ||
| 266 | */ | ||
| 267 | public int hashCode () | ||
| 268 | { | ||
| 269 | 0 | (6) | int hashCode = HashCodeUtil.hash(HashCodeUtil.SEED, mLocalPart); |
| 270 | 0 | return HashCodeUtil.hash(hashCode, mDomain); | |
| 271 | } | ||
| 272 | } |