/* * $Id$ * * Copyright 2006, The jCoderZ.org Project. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the jCoderZ.org Project nor the names of * its contributors may be used to endorse or promote products * derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.jcoderz.commons.types; import java.io.Serializable; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.jcoderz.commons.ArgumentMalformedException; import org.jcoderz.commons.util.Assert; import org.jcoderz.commons.util.HashCodeUtil; import org.jcoderz.commons.util.StringUtil; /** * This class represents an email addresses compliant to RFC 2822, * chapter 3.4.1. Addr-spec specification. * This class does not support obsolete addressing (see ch. 4.4. * Obsolete Addressing). * * <pre> * atext = ALPHA / DIGIT / ; Any character except controls, * "!" / "#" / ; SP, and specials. * "$" / "%" / ; Used for atoms * "&" / "'" / * "*" / "+" / * "-" / "/" / * "=" / "?" / * "^" / "_" / * "`" / "{" / * "|" / "}" / * "~" * atom = [CFWS] 1*atext [CFWS] * dot-atom = [CFWS] dot-atom-text [CFWS] * dot-atom-text = 1*atext *("." 1*atext) * addr-spec = local-part "@" domain * local-part = dot-atom / quoted-string * qtext = NO-WS-CTL / ; Non white space controls * %d33 / ; The rest of the US-ASCII * %d35-91 / ; characters not including "\" * %d93-126 ; or the quote character * qcontent = qtext / quoted-pair * quoted-string = [CFWS] * DQUOTE *([FWS] qcontent) [FWS] DQUOTE * [CFWS] * domain = dot-atom / domain-literal * domain-literal = [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS] * dcontent = dtext / quoted-pair * dtext = NO-WS-CTL / ; Non white space controls * %d33-90 / ; The rest of the US-ASCII * %d94-126 ; characters not including "[", * ; "]", or "\" * FWS = ([*WSP CRLF] 1*WSP) / ; Folding white space * obs-FWS * ctext = NO-WS-CTL / ; Non white space controls * %d33-39 / ; The rest of the US-ASCII * %d42-91 / ; characters not including "(", * %d93-126 ; ")", or "\" * ccontent = ctext / quoted-pair / comment * comment = "(" *([FWS] ccontent) [FWS] ")" * CFWS = *([FWS] comment) (([FWS] comment) / FWS) * </pre> * * <p>The maximum length of an email address is: * 64+1+255 characters (local-part + @ + domain). * The minimum length of an email address is: * 1+1+4 characters (local-part + @ + domain).</p> * * <p>A valid list of top-level domains is defined by the IANA. A top-level * domain which is not part of the * <a href="http://data.iana.org/TLD/tlds-alpha-by-domain.txt">official list</a> * will be rejected.</p> * * @author Michael Rumpf */ public class EmailAddress implements Serializable { private static final long serialVersionUID = 1L; private static final int MAX_LENGTH_LOCAL_PART = 64; private static final int MAX_LENGTH_DOMAIN = 255; // RFC 2822 token definitions for a valid email private static final String SP = "!#$%&'*+-/=?^_`{|}~"; private static final String ATEXT = "[a-zA-Z0-9" + SP + "]"; private static final String ATOM = ATEXT + "+"; // one or more atext chars private static final String DOT_ATOM = "\\." + ATOM; // one atom followed by 0 or more dotAtoms. private static final String LOCAL_PART = ATOM + "(" + DOT_ATOM + ")*"; // RFC 1035 tokens for domain names: private static final String LETTER = "[a-zA-Z]"; private static final String LET_DIG = "[a-zA-Z0-9]"; private static final String LET_DIG_HYP = "[a-zA-Z0-9-]"; private static final String RFC_LABEL = LET_DIG + LET_DIG_HYP + "{0,61}" + LET_DIG; private static final String DOMAIN = RFC_LABEL + "(\\." + RFC_LABEL + ")*\\." + LETTER + "{2,6}"; //Combined together, these form the allowed email regexp allowed by RFC 2822: private static final String ADDRESS = "^" + LOCAL_PART + "@" + DOMAIN + "$"; private static final Pattern ADDRESS_PATTERN = Pattern.compile(ADDRESS); private final String mLocalPart; private final String mDomain; private final String mTopLevelDomain; /** * This is the constructor of the EmailAddress type. * * @param email The email address. */ public EmailAddress (String email) { Assert.notNull(email, "email"); final String mail = email.trim(); final Matcher matcher = ADDRESS_PATTERN.matcher(email); if (!matcher.matches()) { throw new ArgumentMalformedException("email", email, "EMail pattern does not match the RFC2822 grammar!"); } final int at = mail.indexOf('@'); if (at > MAX_LENGTH_LOCAL_PART) { throw new ArgumentMalformedException("email", email, "The local part is longer than " + MAX_LENGTH_LOCAL_PART + " characters!"); } if (mail.length() - at - 1 > MAX_LENGTH_DOMAIN) { throw new ArgumentMalformedException("email", email, "The domain is longer than " + MAX_LENGTH_DOMAIN + " characters!"); } mLocalPart = mail.substring(0, at); mDomain = mail.substring(at + 1); final int dot = mail.lastIndexOf('.'); mTopLevelDomain = mail.substring(dot + 1); } /** * Factory method for converting a String into an instance of type * EmailAddress. * * @param email the email address to parse * @return An instance of type EmailAddress */ public static EmailAddress fromString(String email) { return new EmailAddress(email); } /** * Returns the local part of the address. * @return the local part of the address. */ public String getName () { return mLocalPart; } /** * Returns the domain name of the address. * @return the domain name of the address. */ public String getDomain () { return mDomain; } /** * Returns the top-level domain name of the address. * @return the top-level domain name of the address. */ public String getTopLevelDomain () { return mTopLevelDomain; } /** * Returns the full email address. * @return the full email address. */ public String getAddress () { return mLocalPart + "@" + mDomain; } /** * Returns the full email address. * @return the full email address. */ public String toString () { return mLocalPart + "@" + mDomain; } /** * Returns the true if this email address equals the other. * @param obj the object to compare to. * @return the true if this email address equals the other. * @see java.lang.Object#equals(java.lang.Object) */ public boolean equals (Object obj) { boolean result = false; if (this == obj) { result = true; } else if (obj instanceof EmailAddress) { final EmailAddress other = (EmailAddress) obj; if (StringUtil.equals(getDomain(), other.getDomain()) && StringUtil.equals(getName(), other.getName())) { result = true; } } return result; } /** * Returns the hash code for this email. * @return the hash code for this email. */ public int hashCode () { int hashCode = HashCodeUtil.hash(HashCodeUtil.SEED, mLocalPart); return HashCodeUtil.hash(hashCode, mDomain); } }