All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.aoindustries.aoserv.client.validator.Email Maven / Gradle / Ivy

There is a newer version: 1.92.0
Show newest version
/*
 * aoserv-client - Java client for the AOServ platform.
 * Copyright (C) 2010-2013, 2016  AO Industries, Inc.
 *     [email protected]
 *     7262 Bull Pen Cir
 *     Mobile, AL 36695
 *
 * This file is part of aoserv-client.
 *
 * aoserv-client is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * aoserv-client is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with aoserv-client.  If not, see .
 */
package com.aoindustries.aoserv.client.validator;

import com.aoindustries.aoserv.client.AOServObject;
import com.aoindustries.aoserv.client.DtoFactory;
import com.aoindustries.io.FastExternalizable;
import com.aoindustries.io.FastObjectInput;
import com.aoindustries.io.FastObjectOutput;
import com.aoindustries.lang.ObjectUtils;
import com.aoindustries.util.Internable;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInput;
import java.io.ObjectInputValidation;
import java.io.ObjectOutput;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * Represents an email address.  Email addresses must:
 * 
    *
  • Be non-null
  • *
  • Be non-empty
  • *
  • Contain a single @, but not at the beginning or end
  • *
  • Local part must adhere to RFC 5322: {@link http://en.wikipedia.org/wiki/E-mail_address#RFC_specification}
  • *
* * @author AO Industries, Inc. */ final public class Email implements Comparable, FastExternalizable, ObjectInputValidation, DtoFactory, Internable { public static final int MAX_LENGTH = 254; public static final int MAX_LOCAL_PART_LENGTH = 64; /** * Validates a complete email address. Splits on @ and calls validate on local part and domain. * * @see #validate(java.lang.String, com.aoindustries.aoserv.client.validator.DomainName) */ public static ValidationResult validate(String email) { // Be non-null if(email==null) return new InvalidResult(ApplicationResources.accessor, "Email.validate.isNull"); // Be non-empty if(email.length()==0) return new InvalidResult(ApplicationResources.accessor, "Email.validate.empty"); int atPos = email.indexOf('@'); if(atPos==-1) return new InvalidResult(ApplicationResources.accessor, "Email.validate.noAt"); return validate(email.substring(0, atPos), email.substring(atPos+1)); } /** * Validates the local part of the email address (before the @ symbol), as well as additional domain rules. */ public static ValidationResult validate(String localPart, String domain) { if(domain!=null) { ValidationResult result = DomainName.validate(domain); if(!result.isValid()) return result; } return validateImpl(localPart, domain); } /** * Validates the local part of the email address (before the @ symbol), as well as additional domain rules. */ public static ValidationResult validate(String localPart, DomainName domain) { return validateImpl(localPart, ObjectUtils.toString(domain)); } private static final boolean[] validChars = new boolean[128]; static { for(int ch=0; ch<128; ch++) { validChars[ch] = (ch>='A' && ch<='Z') || (ch>='a' && ch<='z') || (ch>='0' && ch<='9') || ch=='!' || ch=='#' || ch=='$' || ch=='%' || ch=='&' || ch=='\'' || ch=='*' || ch=='+' || ch=='-' || ch=='/' || ch=='=' || ch=='?' || ch=='^' || ch=='_' || ch=='`' || ch=='{' || ch=='|' || ch=='}' || ch=='~' || ch=='.' // Dot here for completeness, but algorithm below will not use it ; } } /** * Validates the local part of the email address (before the @ symbol), as well as additional domain rules. */ private static ValidationResult validateImpl(String localPart, String domain) { if(localPart==null) return new InvalidResult(ApplicationResources.accessor, "Email.validate.localePart.isNull"); if(domain==null) return new InvalidResult(ApplicationResources.accessor, "Email.validate.domain.isNull"); if(domain.lastIndexOf('.')==-1) return new InvalidResult(ApplicationResources.accessor, "Email.validate.domain.noDot"); if(DomainName.isArpa(domain)) return new InvalidResult(ApplicationResources.accessor, "Email.validate.domain.isArpa"); int len = localPart.length(); int totalLen = len + 1 + domain.length(); if(totalLen>MAX_LENGTH) return new InvalidResult(ApplicationResources.accessor, "Email.validate.tooLong", MAX_LENGTH, totalLen); if(len==0) return new InvalidResult(ApplicationResources.accessor, "Email.validate.localePart.empty"); if(len>MAX_LOCAL_PART_LENGTH) return new InvalidResult(ApplicationResources.accessor, "Email.validate.localePart.tooLong", MAX_LOCAL_PART_LENGTH, len); for(int pos=0; pos=128 || !validChars[ch]) return new InvalidResult(ApplicationResources.accessor, "Email.validate.localePart.invalidCharacter", ch, pos); } return ValidResult.getInstance(); } private static final ConcurrentMap> interned = new ConcurrentHashMap<>(); /** * If email is null, then returns is null. * * @see #valueOf(java.lang.String, com.aoindustries.aoserv.client.validator.DomainName) */ public static Email valueOf(String email) throws ValidationException { if(email==null) return null; // Be non-empty if(email.length()==0) throw new ValidationException(new InvalidResult(ApplicationResources.accessor, "Email.validate.empty")); int atPos = email.indexOf('@'); if(atPos==-1) throw new ValidationException(new InvalidResult(ApplicationResources.accessor, "Email.validate.noAt")); return valueOf(email.substring(0, atPos), DomainName.valueOf(email.substring(atPos+1))); } public static Email valueOf(String localPart, DomainName domain) throws ValidationException { //ConcurrentMap domainMap = interned.get(domain); //if(domainMap!=null) { // Email existing = domainMap.get(localPart); // if(existing!=null) return existing; //} return new Email(localPart, domain); } private String localPart; private DomainName domain; private Email(String localPart, DomainName domain) throws ValidationException { this.localPart = localPart; this.domain = domain; validate(); } private void validate() throws ValidationException { ValidationResult result = validate(localPart, domain.toString()); if(!result.isValid()) throw new ValidationException(result); } @Override public boolean equals(Object O) { if(O==null || !(O instanceof Email)) return false; Email other = (Email)O; return localPart.equals(other.localPart) && domain.equals(other.domain) ; } @Override public int hashCode() { return localPart.hashCode() * 31 + domain.hashCode(); } /** * Sorts by domain and then by local part. */ @Override public int compareTo(Email other) { if(this==other) return 0; int diff = domain.compareTo(other.domain); if(diff!=0) return diff; return AOServObject.compareIgnoreCaseConsistentWithEquals(localPart, other.localPart); } @Override public String toString() { return localPart + '@' + domain; } /** * Interns this email much in the same fashion as String.intern(). * * @see String#intern() */ @Override public Email intern() { try { // Intern the domain DomainName internedDomain = domain.intern(); // Atomically get/create the per-domain map ConcurrentMap domainMap = interned.get(internedDomain); if(domainMap==null) { ConcurrentMap newDomainInterned = new ConcurrentHashMap<>(); domainMap = interned.putIfAbsent(internedDomain, newDomainInterned); if(domainMap==null) domainMap = newDomainInterned; } // Atomically get/create the Email object within the domainMap Email existing = domainMap.get(localPart); if(existing==null) { String internedLocalPart = localPart.intern(); Email addMe = localPart==internedLocalPart && domain==internedDomain ? this : new Email(internedLocalPart, internedDomain); existing = domainMap.putIfAbsent(internedLocalPart, addMe); if(existing==null) existing = addMe; } return existing; } catch(ValidationException err) { // Should not fail validation since original object passed throw new AssertionError(err.getMessage()); } } public String getLocalPart() { return localPart; } public DomainName getDomain() { return domain; } @Override public com.aoindustries.aoserv.client.dto.Email getDto() { return new com.aoindustries.aoserv.client.dto.Email(localPart, domain.getDto()); } // private static final long serialVersionUID = 1812494521843295031L; public Email() { } @Override public long getSerialVersionUID() { return serialVersionUID; } @Override public void writeExternal(ObjectOutput out) throws IOException { FastObjectOutput fastOut = FastObjectOutput.wrap(out); try { fastOut.writeFastUTF(localPart); fastOut.writeObject(domain); } finally { fastOut.unwrap(); } } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { if(localPart!=null) throw new IllegalStateException(); FastObjectInput fastIn = FastObjectInput.wrap(in); try { localPart = fastIn.readFastUTF(); domain = (DomainName)fastIn.readObject(); } finally { fastIn.unwrap(); } } @Override public void validateObject() throws InvalidObjectException { try { validate(); } catch(ValidationException err) { InvalidObjectException newErr = new InvalidObjectException(err.getMessage()); newErr.initCause(err); throw newErr; } } // }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy