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.math.BigInteger;
8   import java.util.Objects;
9   import java.util.regex.Pattern;
10  
11  import de.powerstat.validation.interfaces.IValueObject;
12  import de.powerstat.validation.values.impl.IBANVerifierAbstractFactory;
13  
14  
15  /**
16   * IBAN.
17   *
18   * Probably DSGVO relevant.
19   *
20   * TODO https://openiban.com/
21   * TODO Human format in/out
22   */
23  public final class IBAN implements Comparable<IBAN>, IValueObject
24   {
25    /* *
26     * Cache for singletons.
27     */
28    // private static final Map<String, IBAN> CACHE = new WeakHashMap<>();
29  
30    /**
31     * IBAN regexp.
32     */
33    @SuppressWarnings("java:S5867")
34    private static final Pattern IBAN_REGEXP = Pattern.compile("^[A-Z]{2}\\d{2}[0-9A-Z]{11,30}$"); //$NON-NLS-1$
35  
36    /**
37     * IBAN.
38     */
39    private final String iban;
40  
41  
42    /**
43     * Constructor.
44     *
45     * @param iban IBAN
46     * @throws NullPointerException if iban is null
47     * @throws IllegalArgumentException if iban is not an correct iban
48     */
49    private IBAN(final String iban)
50     {
51      super();
52      Objects.requireNonNull(iban, "iban"); //$NON-NLS-1$
53      if ((iban.length() < 15) || (iban.length() > 34))
54       {
55        throw new IllegalArgumentException("IBAN with wrong length"); //$NON-NLS-1$
56       }
57      if (!IBAN.IBAN_REGEXP.matcher(iban).matches())
58       {
59        throw new IllegalArgumentException("IBAN with wrong format"); //$NON-NLS-1$
60       }
61      final var checksum = iban.substring(2, 4);
62      if ("00".equals(checksum) || "01".equals(checksum) || "99".equals(checksum)) //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
63       {
64        throw new IllegalArgumentException("IBAN with illegal checksum"); //$NON-NLS-1$
65       }
66      if (!verifyChecksum(iban))
67       {
68        throw new IllegalArgumentException("IBAN with wrong checksum"); //$NON-NLS-1$
69       }
70      final var country = Country.of(iban.substring(0, 2));
71      if (!IBANVerifierAbstractFactory.createIBANVerifier(country).verify(iban))
72       {
73        throw new IllegalArgumentException("IBAN not correct in country context: " + iban); //$NON-NLS-1$
74       }
75      this.iban = iban;
76     }
77  
78  
79    /**
80     * Calculate ISO 7064 mod 97-10 checksum.
81     *
82     * @param iban IBAN
83     * @return true when checksum is correct, false otherwise
84     */
85    private static boolean verifyChecksum(final String iban)
86     {
87      final String reordered = iban.substring(4) + iban.substring(0, 2) + iban.substring(2, 4);
88      final String replacement = reordered.replace("A", "10").replace("B", "11").replace("C", "12").replace("D", "13").replace("E", "14").replace("F", "15").replace("G", "16").replace("H", "17").replace("I", "18").replace("J", "19").replace("K", "20").replace("L", "21").replace("M", "22").replace("N", "23").replace("O", "24").replace("P", "25").replace("Q", "26").replace("R", "27").replace("S", "28").replace("T", "29").replace("U", "30").replace("V", "31").replace("W", "32").replace("X", "33").replace("Y", "34").replace("Z", "35"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ //$NON-NLS-11$ //$NON-NLS-12$ //$NON-NLS-13$ //$NON-NLS-14$ //$NON-NLS-15$ //$NON-NLS-16$ //$NON-NLS-17$ //$NON-NLS-18$ //$NON-NLS-19$ //$NON-NLS-20$ //$NON-NLS-21$ //$NON-NLS-22$ //$NON-NLS-23$ //$NON-NLS-24$ //$NON-NLS-25$ //$NON-NLS-26$ //$NON-NLS-27$ //$NON-NLS-28$ //$NON-NLS-29$ //$NON-NLS-30$ //$NON-NLS-31$ //$NON-NLS-32$ //$NON-NLS-33$ //$NON-NLS-34$ //$NON-NLS-35$ //$NON-NLS-36$ //$NON-NLS-37$ //$NON-NLS-38$ //$NON-NLS-39$ //$NON-NLS-40$ //$NON-NLS-41$ //$NON-NLS-42$ //$NON-NLS-43$ //$NON-NLS-44$ //$NON-NLS-45$ //$NON-NLS-46$ //$NON-NLS-47$ //$NON-NLS-48$ //$NON-NLS-49$ //$NON-NLS-50$ //$NON-NLS-51$ //$NON-NLS-52$
89      final var num = new BigInteger(replacement);
90      final BigInteger result = num.remainder(BigInteger.valueOf(97));
91      return result.longValue() == 1;
92     }
93  
94  
95    /**
96     * IBAN factory.
97     *
98     * @param iban IBAN
99     * @return IBAN object
100    */
101   public static IBAN of(final String iban)
102    {
103     /*
104     synchronized (IBAN.class)
105      {
106       IBAN obj = IBAN.CACHE.get(iban);
107       if (obj != null)
108        {
109         return obj;
110        }
111       obj = new IBAN(iban);
112       IBAN.CACHE.put(iban, obj);
113       return obj;
114      }
115     */
116     return new IBAN(iban);
117    }
118 
119 
120   /**
121    * Returns the value of this IBAN as a string.
122    *
123    * @return The text value represented by this object after conversion to type string.
124    */
125   @Override
126   public String stringValue()
127    {
128     return this.iban;
129    }
130 
131 
132   /**
133    * Calculate hash code.
134    *
135    * @return Hash
136    * @see java.lang.Object#hashCode()
137    */
138   @Override
139   public int hashCode()
140    {
141     return this.iban.hashCode();
142    }
143 
144 
145   /**
146    * Is equal with another object.
147    *
148    * @param obj Object
149    * @return true when equal, false otherwise
150    * @see java.lang.Object#equals(java.lang.Object)
151    */
152   @Override
153   public boolean equals(final Object obj)
154    {
155     if (this == obj)
156      {
157       return true;
158      }
159     if (!(obj instanceof IBAN))
160      {
161       return false;
162      }
163     final IBAN other = (IBAN)obj;
164     return this.iban.equals(other.iban);
165    }
166 
167 
168   /**
169    * Returns the string representation of this IBAN.
170    *
171    * The exact details of this representation are unspecified and subject to change, but the following may be regarded as typical:
172    *
173    * "IBAN[iban=DE68210501700012345678]"
174    *
175    * @return String representation of this IBAN
176    * @see java.lang.Object#toString()
177    */
178   @Override
179   public String toString()
180    {
181     final var builder = new StringBuilder();
182     builder.append("IBAN[iban=").append(this.iban).append(']'); //$NON-NLS-1$
183     return builder.toString();
184    }
185 
186 
187   /**
188    * Compare with another object.
189    *
190    * @param obj Object to compare with
191    * @return 0: equal; 1: greater; -1: smaller
192    * @see java.lang.Comparable#compareTo(java.lang.Object)
193    */
194   @Override
195   public int compareTo(final IBAN obj)
196    {
197     Objects.requireNonNull(obj, "obj"); //$NON-NLS-1$
198     return this.iban.compareTo(obj.iban);
199    }
200 
201  }