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.Objects;
8   import java.util.regex.Pattern;
9   
10  import de.powerstat.validation.interfaces.IValueObject;
11  
12  
13  /**
14   * Address Building number.
15   *
16   * Possibly DSGVO relevant.
17   *
18   * TODO optimize constructor/compareTo
19   */
20  public final class BuildingNr implements Comparable<BuildingNr>, IValueObject
21   {
22    /* *
23     * Logger.
24     */
25    // private static final Logger LOGGER = LogManager.getLogger(BuildingNr.class);
26  
27    /* *
28     * Cache for singletons.
29     */
30    // private static final Map<String, BuildingNr> CACHE = new WeakHashMap<>();
31  
32    /**
33     * Building nr regexp.
34     */
35    private static final Pattern BUILDINGNR_REGEXP = Pattern.compile("^(\\d{1,5})(([-/])(\\d{1,5}))?( (\\d{1,3})/(\\d{1,3}))?( ([a-z]))?$"); //$NON-NLS-1$
36  
37    /**
38     * Maximum known building nr in the world.
39     */
40    private static final int MAX_KNOWN_BUILDING_NR = 29999;
41  
42    /**
43     * Building number.
44     */
45    private final String buildingNr;
46  
47  
48    /**
49     * Constructor.
50     *
51     * @param buildingNr Building number
52     * @throws NullPointerException if buildingNr is null
53     * @throws IllegalArgumentException if buildingNr is not an correct buildingNr
54     */
55    private BuildingNr(final String buildingNr)
56     {
57      super();
58      Objects.requireNonNull(buildingNr, "buildingNr"); //$NON-NLS-1$
59      if ((buildingNr.length() < 1) || (buildingNr.length() > 21))
60       {
61        throw new IllegalArgumentException("BuildingNr with wrong length"); //$NON-NLS-1$
62       }
63      final var matcher = BuildingNr.BUILDINGNR_REGEXP.matcher(buildingNr);
64      if (!matcher.matches())
65       {
66        throw new IllegalArgumentException("BuildingNr with wrong format"); //$NON-NLS-1$
67       }
68      // group 1: building nr (from) 42:   42
69      // group 4: building nr (to) 42-43:  43
70      // group 6: numerator 3/4:         3
71      // group 7: denominator 3/4:       4
72      // group 8: alphabetic character:  a
73      if (Integer.parseInt(matcher.group(1)) > BuildingNr.MAX_KNOWN_BUILDING_NR)
74       {
75        throw new IllegalArgumentException("BuildingNr > " + BuildingNr.MAX_KNOWN_BUILDING_NR); //$NON-NLS-1$
76       }
77      if ((matcher.group(4) != null) && (Integer.compare(Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(4))) >= 0))
78       {
79        throw new IllegalArgumentException("BuildingNr from >= BuildingNr to"); //$NON-NLS-1$
80       }
81      if ((matcher.group(7) != null) && (Integer.compare(Integer.parseInt(matcher.group(6)), Integer.parseInt(matcher.group(7))) > 0))
82       {
83        throw new IllegalArgumentException("BuildingNr numerator > denominator"); //$NON-NLS-1$
84       }
85      this.buildingNr = buildingNr;
86     }
87  
88  
89    /**
90     * BuildingNr factory.
91     *
92     * @param buildingNr Building number
93     * @return BuildingNr object
94     */
95    public static BuildingNr of(final String buildingNr)
96     {
97      /*
98      synchronized (BuildingNr.class)
99       {
100       BuildingNr obj = BuildingNr.CACHE.get(buildingNr);
101       if (obj != null)
102        {
103         return obj;
104        }
105       obj = new BuildingNr(buildingNr);
106       BuildingNr.CACHE.put(buildingNr, obj);
107       return obj;
108      }
109     */
110     return new BuildingNr(buildingNr);
111    }
112 
113 
114   /**
115    * Returns the value of this BuildingNr as a string.
116    *
117    * @return The text value represented by this object after conversion to type string.
118    */
119   @Override
120   public String stringValue()
121    {
122     return this.buildingNr;
123    }
124 
125 
126   /**
127    * Calculate hash code.
128    *
129    * @return Hash
130    * @see java.lang.Object#hashCode()
131    */
132   @Override
133   public int hashCode()
134    {
135     return this.buildingNr.hashCode();
136    }
137 
138 
139   /**
140    * Is equal with another object.
141    *
142    * @param obj Object
143    * @return true when equal, false otherwise
144    * @see java.lang.Object#equals(java.lang.Object)
145    */
146   @Override
147   public boolean equals(final Object obj)
148    {
149     if (this == obj)
150      {
151       return true;
152      }
153     if (!(obj instanceof BuildingNr))
154      {
155       return false;
156      }
157     final BuildingNr other = (BuildingNr)obj;
158     return this.buildingNr.equals(other.buildingNr);
159    }
160 
161 
162   /**
163    * Returns the string representation of this BuildingNr.
164    *
165    * The exact details of this representation are unspecified and subject to change, but the following may be regarded as typical:
166    *
167    * "BuildingNr[buildingNr=42]"
168    *
169    * @return String representation of this BuildingNr
170    * @see java.lang.Object#toString()
171    */
172   @Override
173   public String toString()
174    {
175     final var builder = new StringBuilder(23);
176     builder.append("BuildingNr[buildingNr=").append(this.buildingNr).append(']'); //$NON-NLS-1$
177     return builder.toString();
178    }
179 
180 
181   /**
182    * Compare with another object.
183    *
184    * @param obj Object to compare with
185    * @return 0: equal; 1: greater; -1: smaller
186    * @see java.lang.Comparable#compareTo(java.lang.Object)
187    *
188    * TODO optimize (do regexp in constructor)
189    */
190   @Override
191   public int compareTo(final BuildingNr obj)
192    {
193     Objects.requireNonNull(obj, "obj"); //$NON-NLS-1$
194     final var matcher1 = BuildingNr.BUILDINGNR_REGEXP.matcher(this.buildingNr);
195     final var matcher2 = BuildingNr.BUILDINGNR_REGEXP.matcher(obj.buildingNr);
196     /* boolean result1 = */ matcher1.matches();
197     /* boolean result2 = */ matcher2.matches();
198     // group 1: building nr (from) 42:   42
199     // group 4: building nr (to) 42-43:  43
200     // group 6: numerator 3/4:         3
201     // group 7: denominator 3/4:       4
202     // group 8: alphabetic character:  a
203     int result = Integer.compare(Integer.parseInt(matcher1.group(1)), Integer.parseInt(matcher2.group(1)));
204     if (result == 0)
205      {
206       if ((matcher1.group(7) != null) || (matcher2.group(7) != null)) // 3/4
207        {
208         if (matcher1.group(7) == null)
209          {
210           return -1;
211          }
212         if (matcher2.group(7) == null)
213          {
214           return 1;
215          }
216         if (matcher1.group(7).compareTo(matcher2.group(7)) != 0)
217          {
218           throw new IllegalStateException("BuildingNrs do not have the same denominator"); //$NON-NLS-1$
219          }
220         result = Integer.compare(Integer.parseInt(matcher1.group(6)), Integer.parseInt(matcher2.group(6)));
221         if (result != 0)
222          {
223           return result;
224          }
225        }
226       if ((matcher1.group(8) != null) || (matcher2.group(8) != null)) // a-z
227        {
228         if (matcher1.group(8) == null)
229          {
230           result = -1;
231          }
232         else if (matcher2.group(8) == null)
233          {
234           result = 1;
235          }
236         else
237          {
238           result = matcher1.group(8).compareTo(matcher2.group(8));
239          }
240        }
241      }
242     return result;
243    }
244 
245  }