View Javadoc
1   /*
2    * Copyright (C) 2020-2023 Dipl.-Inform. Kai Hofmann. All rights reserved!
3    */
4   package de.powerstat.validation.values;
5   
6   
7   import java.util.Locale;
8   import java.util.Objects;
9   import java.util.regex.Pattern;
10  
11  import de.powerstat.validation.interfaces.IValueObject;
12  
13  
14  /**
15   * Electronic mail.
16   *
17   * Probably DSGVO relevant.
18   *
19   * TODO Hostname exists?
20   * TODO email exists check
21   */
22  public final class EMail implements Comparable<EMail>, IValueObject
23   {
24    /* *
25     * Cache for singletons.
26     */
27    // private static final Map<String, EMail> CACHE = new WeakHashMap<>();
28  
29    /**
30     * Local part regexp.
31     */
32    private static final Pattern LOCAL_REGEXP = Pattern.compile("^[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]+$"); //$NON-NLS-1$
33  
34    /**
35     * EMail.
36     */
37    private final String email;
38  
39    /**
40     * EMails domain part.
41     */
42    private final Hostname domainPart;
43  
44    /**
45     * EMails local part.
46     */
47    private final String localPart;
48  
49  
50    /**
51     * Constructor.
52     *
53     * Comments, double quotes and UTF-8 characters within the emails local part are not yet supported.
54     *
55     * @param email EMail
56     * @throws NullPointerException if email is null
57     * @throws IllegalArgumentException if email is not an supported email address
58     */
59    private EMail(final String email)
60     {
61      super();
62      Objects.requireNonNull(email, "email"); //$NON-NLS-1$
63      if ((email.length() < 6) || (email.length() > 254))
64       {
65        throw new IllegalArgumentException("To short or long for an email address"); //$NON-NLS-1$
66       }
67      final String[] parts = email.split("@"); //$NON-NLS-1$
68      if (parts.length != 2)
69       {
70        throw new IllegalArgumentException("Not an email address, missing or to much @"); //$NON-NLS-1$
71       }
72      if (parts[0].length() > 64)
73       {
74        throw new IllegalArgumentException("Local part greater than 64 characters"); //$NON-NLS-1$
75       }
76      if (parts[1].charAt(0) == '[')
77       {
78        parts[1] = (parts[1].toLowerCase(Locale.getDefault()).startsWith("[ipv6:")) ? parts[1].substring(6) : parts[1].substring(1); //$NON-NLS-1$
79        if (!parts[1].endsWith("]")) //$NON-NLS-1$
80         {
81          throw new IllegalArgumentException("Missing end of IPv4/IPv6 address"); //$NON-NLS-1$
82         }
83        parts[1] = parts[1].substring(0, parts[1].length() - 1);
84       }
85      this.domainPart = Hostname.of(parts[1]); // Check hostname and store for isReachable
86      if ((parts[0].charAt(0) == '(') || (parts[0].charAt(parts[0].length() - 1) == ')'))
87       {
88        throw new IllegalArgumentException("Comments in email addresses are not supported"); //$NON-NLS-1$
89       }
90      if (parts[0].indexOf('"') > -1)
91       {
92        throw new IllegalArgumentException("Double quotes in email addresses are not supported"); //$NON-NLS-1$
93       }
94      if ((parts[0].charAt(0) == '.') || (parts[0].charAt(parts[0].length() - 1) == '.'))
95       {
96        throw new IllegalArgumentException("A dot is not allowed at start or end of an emails local part"); //$NON-NLS-1$
97       }
98      if (parts[0].contains("..")) //$NON-NLS-1$
99       {
100       throw new IllegalArgumentException("Two or more dots behind each other are not allowed within an emails local part"); //$NON-NLS-1$
101      }
102     if (!EMail.LOCAL_REGEXP.matcher(parts[0]).matches())
103      {
104       throw new IllegalArgumentException("Illegal character found in emails local part or unsupported UTF-8 character"); //$NON-NLS-1$
105      }
106     this.localPart = parts[0]; // Store for check receiver
107     this.email = email;
108    }
109 
110 
111   /**
112    * EMail factory.
113    *
114    * @param email EMail
115    * @return EMail object
116    */
117   public static EMail of(final String email)
118    {
119     /*
120     synchronized (EMail.class)
121      {
122       EMail obj = EMail.CACHE.get(email);
123       if (obj != null)
124        {
125         return obj;
126        }
127       obj = new EMail(email);
128       EMail.CACHE.put(email, obj);
129       return obj;
130      }
131     */
132     return new EMail(email);
133    }
134 
135 
136   /**
137    * Returns the value of this EMail as a string.
138    *
139    * @return The text value represented by this object after conversion to type string.
140    */
141   @Override
142   public String stringValue()
143    {
144     return this.email;
145    }
146 
147 
148   /**
149    * Get emails domain part string.
150    *
151    * @return Domain part string
152    */
153   public String getDomainPart()
154    {
155     return this.domainPart.stringValue();
156    }
157 
158 
159   /**
160    * Get emails reverse domain part string.
161    *
162    * @return Reverse domain part string
163    */
164   public String getReverseDomainPart()
165    {
166     return this.domainPart.getReverseHostname();
167    }
168 
169 
170   /**
171    * Get emails local part string.
172    *
173    * @return Local part string
174    */
175   public String getLocalPart()
176    {
177     return this.localPart;
178    }
179 
180 
181   /**
182    * Calculate hash code.
183    *
184    * @return Hash
185    * @see java.lang.Object#hashCode()
186    */
187   @Override
188   public int hashCode()
189    {
190     return this.email.hashCode();
191    }
192 
193 
194   /**
195    * Is equal with another object.
196    *
197    * @param obj Object
198    * @return true when equal, false otherwise
199    * @see java.lang.Object#equals(java.lang.Object)
200    */
201   @Override
202   public boolean equals(final Object obj)
203    {
204     if (this == obj)
205      {
206       return true;
207      }
208     if (!(obj instanceof EMail))
209      {
210       return false;
211      }
212     final EMail other = (EMail)obj;
213     return this.email.equals(other.email);
214    }
215 
216 
217   /**
218    * Returns the string representation of this EMail.
219    *
220    * The exact details of this representation are unspecified and subject to change, but the following may be regarded as typical:
221    *
222    * "EMail[email=user@example.com]"
223    *
224    * @return String representation of this EMail
225    * @see java.lang.Object#toString()
226    */
227   @Override
228   public String toString()
229    {
230     final var builder = new StringBuilder();
231     builder.append("EMail[email=").append(this.email).append(']'); //$NON-NLS-1$
232     return builder.toString();
233    }
234 
235 
236   /**
237    * Compare with another object.
238    *
239    * @param obj Object to compare with
240    * @return 0: equal; 1: greater; -1: smaller
241    * @see java.lang.Comparable#compareTo(java.lang.Object)
242    */
243   @Override
244   public int compareTo(final EMail obj)
245    {
246     Objects.requireNonNull(obj, "obj"); //$NON-NLS-1$
247     return this.email.compareTo(obj.email); // TODO hostname, username
248    }
249 
250  }