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.io.IOException;
8   import java.net.InetAddress;
9   import java.net.UnknownHostException;
10  import java.util.Objects;
11  import java.util.regex.Pattern;
12  
13  import org.apache.logging.log4j.LogManager;
14  import org.apache.logging.log4j.Logger;
15  
16  import de.powerstat.validation.generated.GeneratedTlds;
17  import de.powerstat.validation.interfaces.IValueObject;
18  
19  
20  /**
21   * Hostname.
22   *
23   * Probably DSGVO relevant.
24   *
25   * TODO Verify TopLevelDomain
26   * TODO ping ok?
27   */
28  public final class Hostname implements Comparable<Hostname>, IValueObject
29   {
30    /**
31     * Logger.
32     */
33    private static final Logger LOGGER = LogManager.getLogger(Hostname.class);
34  
35    /* *
36     * Cache for singletons.
37     */
38    // private static final Map<String, Hostname> CACHE = new WeakHashMap<>();
39  
40    /**
41     * Hostname regexp.
42     */
43    private static final Pattern HOSTNAME_REGEXP = Pattern.compile("^[.0-9a-zA-Z-]+$"); //$NON-NLS-1$
44  
45    /**
46     * Escaped dot.
47     */
48    private static final String ESC_DOT = "\\."; //$NON-NLS-1$
49  
50    /**
51     * Hostname by dots regexp.
52     */
53    private static final Pattern HOSTNAME_BY_DOTS = Pattern.compile(Hostname.ESC_DOT);
54  
55    /**
56     * Hostname.
57     */
58    private final String hostname;
59  
60    /**
61     * Reverse hostname.
62     */
63    @SuppressWarnings("PMD.AvoidFieldNameMatchingMethodName")
64    private final String reverseHostname;
65  
66  
67    /**
68     * Constructor.
69     *
70     * @param hostname Hostname
71     * @throws NullPointerException if hostname is null
72     * @throws IllegalArgumentException if hostname is not a hostname
73     */
74    private Hostname(final String hostname)
75     {
76      super();
77      Objects.requireNonNull(hostname, "hostname"); //$NON-NLS-1$
78      if ((hostname.length() < 2) || (hostname.length() > 253))
79       {
80        throw new IllegalArgumentException("To short or long for a hostname"); //$NON-NLS-1$
81       }
82      var tempHostname = ""; //$NON-NLS-1$
83      try
84       {
85        tempHostname = IPV4Address.of(hostname).stringValue();
86       }
87      catch (final IllegalArgumentException ignored)
88       {
89        // LOGGER.debug("IllegalArgumentException", ignored);
90       }
91      try
92       {
93        if (tempHostname.isEmpty())
94         {
95          tempHostname = IPV6Address.of(hostname).stringValue();
96         }
97       }
98      catch (final IllegalArgumentException ignored)
99       {
100       // LOGGER.debug("IllegalArgumentException", ignored);
101      }
102     if (tempHostname.isEmpty())
103      {
104       tempHostname = checkHostname(hostname);
105       this.reverseHostname = reverseHostname(tempHostname);
106      }
107     else
108      {
109       this.reverseHostname = tempHostname;
110      }
111     this.hostname = tempHostname;
112    }
113 
114 
115   /**
116    * Check hostname.
117    *
118    * @param hostname Hostname
119    * @return Hostname
120    */
121   private static String checkHostname(final String hostname)
122    {
123     if (!Hostname.HOSTNAME_REGEXP.matcher(hostname).matches())
124      {
125       throw new IllegalArgumentException("Hostname contains illegal character"); //$NON-NLS-1$
126      }
127     final String[] parts = HOSTNAME_BY_DOTS.split(hostname, 0);
128     // final String[] parts = hostname.split(Hostname.ESC_DOT);
129     if (parts.length < 2)
130      {
131       throw new IllegalArgumentException("Hostname must be at a minimum consist of subdomain.topleveldomain"); //$NON-NLS-1$
132      }
133     for (final String part : parts)
134      {
135       if (part.isEmpty() || (part.length() > 63))
136        {
137         throw new IllegalArgumentException("Hostname part empty or to long"); //$NON-NLS-1$
138        }
139       if ((part.charAt(0) == '-') || (part.charAt(part.length() - 1) == '-'))
140        {
141         throw new IllegalArgumentException("Illegal character '-' at name start or end"); //$NON-NLS-1$
142        }
143      }
144     if (!GeneratedTlds.contains(parts[parts.length - 1]))
145      {
146       throw new IllegalArgumentException("Unknown top level domain in hostname"); //$NON-NLS-1$
147      }
148     return hostname;
149    }
150 
151 
152   /**
153    * Reverse hostname.
154    *
155    * @param hostname Hostname
156    * @return Reversed hostname.
157    */
158   private static String reverseHostname(final String hostname)
159    {
160     final String[] parts = HOSTNAME_BY_DOTS.split(hostname, 0);
161     // final String[] parts = hostname.split(Hostname.ESC_DOT);
162     final var buffer = new StringBuilder(hostname.length());
163     for (int i = parts.length - 1; i >= 0; --i)
164      {
165       if (buffer.length() != 0)
166        {
167         buffer.append('.');
168        }
169       buffer.append(parts[i]);
170      }
171     return buffer.toString();
172    }
173 
174 
175   /**
176    * Hostname factory.
177    *
178    * @param hostname Hostname
179    * @return Hostname object
180    */
181   public static Hostname of(final String hostname)
182    {
183     /*
184     synchronized (Hostname.class)
185      {
186       Hostname obj = Hostname.CACHE.get(hostname);
187       if (obj != null)
188        {
189         return obj;
190        }
191       obj = new Hostname(hostname);
192       Hostname.CACHE.put(hostname, obj);
193       return obj;
194      }
195     */
196     return new Hostname(hostname);
197    }
198 
199 
200   /**
201    * Returns the value of this Hostname as a string.
202    *
203    * @return The text value represented by this object after conversion to type string.
204    */
205   @Override
206   public String stringValue()
207    {
208     return this.hostname;
209    }
210 
211 
212   /**
213    * Get reverse hostname string.
214    *
215    * @return Reverse hostname string
216    */
217   public String getReverseHostname()
218    {
219     return this.reverseHostname;
220    }
221 
222 
223   /**
224    * Exist hostname.
225    *
226    * @return true if hostname was found, false otherwise
227    */
228   public boolean exist()
229    {
230     try
231      {
232       /* final InetAddress address = */ InetAddress.getByName(this.hostname);
233      }
234     catch (final UnknownHostException ignored)
235      {
236       // LOGGER.debug("UnknownHostException", ignored);
237       return false;
238      }
239     return true;
240    }
241 
242 
243   /**
244    * Is hostname reachable.
245    *
246    * @param timeout Timeout in milliseconds.
247    * @return true: Hostname is reachable; false otherwise
248    */
249   public boolean isReachable(final int timeout)
250    {
251     try
252      {
253       final var address = InetAddress.getByName(this.hostname);
254       return address.isReachable(timeout);
255      }
256     catch (final IOException ignored)
257      {
258       // LOGGER.debug("IOException", ignored);
259       return false;
260      }
261    }
262 
263 
264   /**
265    * Calculate hash code.
266    *
267    * @return Hash
268    * @see java.lang.Object#hashCode()
269    */
270   @Override
271   public int hashCode()
272    {
273     return this.hostname.hashCode();
274    }
275 
276 
277   /**
278    * Is equal with another object.
279    *
280    * @param obj Object
281    * @return true when equal, false otherwise
282    * @see java.lang.Object#equals(java.lang.Object)
283    */
284   @Override
285   public boolean equals(final Object obj)
286    {
287     if (this == obj)
288      {
289       return true;
290      }
291     if (!(obj instanceof Hostname))
292      {
293       return false;
294      }
295     final Hostname other = (Hostname)obj;
296     return this.hostname.equals(other.hostname);
297    }
298 
299 
300   /**
301    * Returns the string representation of this Hostname.
302    *
303    * The exact details of this representation are unspecified and subject to change, but the following may be regarded as typical:
304    *
305    * "Hostname[hostname=192.168.0.0]"
306    *
307    * @return String representation of this Hostname
308    * @see java.lang.Object#toString()
309    */
310   @Override
311   public String toString()
312    {
313     final var builder = new StringBuilder(19);
314     builder.append("Hostname[hostname=").append(this.hostname).append(']'); //$NON-NLS-1$
315     return builder.toString();
316    }
317 
318 
319   /**
320    * Compare with another object.
321    *
322    * @param obj Object to compare with
323    * @return 0: equal; 1: greater; -1: smaller
324    * @see java.lang.Comparable#compareTo(java.lang.Object)
325    */
326   @Override
327   public int compareTo(final Hostname obj)
328    {
329     Objects.requireNonNull(obj, "obj"); //$NON-NLS-1$
330     return this.hostname.compareTo(obj.hostname);
331    }
332 
333  }