View Javadoc
1   /*
2    *  jDTAUS Banking API
3    *  Copyright (C) 2005 Christian Schulte
4    *  <cs@schulte.it>
5    *
6    *  This library is free software; you can redistribute it and/or
7    *  modify it under the terms of the GNU Lesser General Public
8    *  License as published by the Free Software Foundation; either
9    *  version 2.1 of the License, or any later version.
10   *
11   *  This library is distributed in the hope that it will be useful,
12   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   *  Lesser General Public License for more details.
15   *
16   *  You should have received a copy of the GNU Lesser General Public
17   *  License along with this library; if not, write to the Free Software
18   *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19   *
20   */
21  package org.jdtaus.banking;
22  
23  import java.lang.ref.Reference;
24  import java.lang.ref.SoftReference;
25  import java.text.DecimalFormat;
26  import java.text.ParseException;
27  import java.text.ParsePosition;
28  import java.util.Collections;
29  import java.util.HashMap;
30  import java.util.Map;
31  
32  /**
33   * Unique entity identifier.
34   * <p>A Referenznummer10 is a positive integer with a maximum of ten digits.</p>
35   *
36   * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
37   * @version $JDTAUS: Referenznummer10.java 8661 2012-09-27 11:29:58Z schulte $
38   */
39  public final class Referenznummer10 extends Number implements Comparable
40  {
41  
42      /**
43       * Constant for the electronic format of a Referenznummer10.
44       * <p>The electronic format of a Referenznummer10 is a ten digit number with leading zeros omitted (e.g. 6789).</p>
45       */
46      public static final int ELECTRONIC_FORMAT = 5001;
47  
48      /**
49       * Constant for the letter format of a Referenznummer10.
50       * <p>The letter format of a Referenznummer10 is a ten digit number with leading zeros omitted separated by spaces
51       * between the first three digits and the second three digits, the second three digits and the third three digits,
52       * and between the third three digits and the last digit (e.g. 123 456 789 0).</p>
53       */
54      public static final int LETTER_FORMAT = 5002;
55  
56      /** Maximum number of digits of a Referenznummer10. */
57      public static final int MAX_DIGITS = 10;
58  
59      /** Maximum number of characters of a Referenznummer10. */
60      public static final int MAX_CHARACTERS = 13;
61  
62      /** {@code 10^0..10^9}. */
63      private static final double[] EXP10 =
64      {
65          1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000,
66          1000000000
67      };
68  
69      /** Serial version UID for backwards compatibility with 1.0.x classes. */
70      private static final long serialVersionUID = -72660089907415650L;
71  
72      /** Used to cache instances. */
73      private static volatile Reference cacheReference = new SoftReference( null );
74  
75      /**
76       * Reference code.
77       * @serial
78       */
79      private long ref;
80  
81      /**
82       * Creates a new {@code Referenznummer10} instance.
83       *
84       * @param referenceCode The long to create an instance from.
85       *
86       * @throws IllegalArgumentException if {@code referenceCode} is negative, zero or greater than 9999999999.
87       *
88       * @see #checkReferenznummer10(Number)
89       */
90      private Referenznummer10( final Number referenceCode )
91      {
92          if ( !Referenznummer10.checkReferenznummer10( referenceCode ) )
93          {
94              throw new IllegalArgumentException( referenceCode.toString() );
95          }
96  
97          this.ref = referenceCode.longValue();
98      }
99  
100     /**
101      * Parses text from a string to produce a {@code Referenznummer10}.
102      * <p>The method attempts to parse text starting at the index given by {@code pos}. If parsing succeeds, then the
103      * index of {@code pos} is updated to the index after the last character used (parsing does not necessarily use all
104      * characters up to the end of the string), and the parsed value is returned. The updated {@code pos} can be used to
105      * indicate the starting point for the next call to this method.</p>
106      *
107      * @param referenceCode A Referenznummer10 in either electronic or letter format.
108      * @param pos A {@code ParsePosition} object with index and error index information as described above.
109      *
110      * @return The parsed value, or {@code null} if the parse fails.
111      *
112      * @throws NullPointerException if either {@code referenceCode} or {@code pos} is {@code null}.
113      */
114     public static Referenznummer10 parse( final String referenceCode, final ParsePosition pos )
115     {
116         if ( referenceCode == null )
117         {
118             throw new NullPointerException( "referenceCode" );
119         }
120         if ( pos == null )
121         {
122             throw new NullPointerException( "pos" );
123         }
124 
125         Referenznummer10 ret = null;
126         boolean sawSpace = false;
127         boolean failed = false;
128 
129         final ParsePosition fmtPos = new ParsePosition( 0 );
130         final int len = referenceCode.length();
131         final int startIndex = pos.getIndex();
132         final int maxIndex = startIndex + MAX_CHARACTERS;
133         final StringBuffer digits = new StringBuffer( MAX_DIGITS );
134         int mode = ELECTRONIC_FORMAT;
135         int part = 0;
136         int partStart = 0;
137         int partEnd = 2;
138         int digit = 0;
139         int i = startIndex;
140 
141         for ( ; i < len && i < maxIndex && digits.length() < MAX_DIGITS; i++ )
142         {
143             final char c = referenceCode.charAt( i );
144 
145             if ( Character.isDigit( c ) )
146             {
147                 sawSpace = false;
148 
149                 if ( mode == LETTER_FORMAT )
150                 {
151                     if ( digit < partStart || digit > partEnd )
152                     {
153                         failed = true;
154                     }
155                     else
156                     {
157                         digits.append( c );
158                     }
159                 }
160                 else
161                 {
162                     digits.append( c );
163                 }
164 
165                 digit++;
166             }
167             else if ( c == ' ' )
168             {
169                 if ( sawSpace || i == startIndex || ( mode == ELECTRONIC_FORMAT && digit != 3 ) )
170                 {
171                     failed = true;
172                 }
173                 else
174                 {
175                     mode = LETTER_FORMAT;
176                     switch ( part )
177                     {
178                         case 0:
179                             partStart = 3;
180                             partEnd = 5;
181                             break;
182                         case 1:
183                             partStart = 6;
184                             partEnd = 8;
185                             break;
186                         case 2:
187                             partStart = 9;
188                             partEnd = 9;
189                             break;
190                         default:
191                             failed = true;
192                             break;
193                     }
194                     part++;
195 
196                     if ( digit < partStart || digit > partEnd )
197                     {
198                         failed = true;
199                     }
200                 }
201 
202                 sawSpace = true;
203             }
204             else
205             {
206                 failed = true;
207             }
208 
209             if ( failed )
210             {
211                 pos.setErrorIndex( i );
212                 break;
213             }
214         }
215 
216         if ( !failed )
217         {
218             final Number num = new DecimalFormat( "##########" ).parse( digits.toString(), fmtPos );
219             if ( num != null && fmtPos.getErrorIndex() == -1 )
220             {
221                 final String key = num.toString();
222                 ret = (Referenznummer10) getCache().get( key );
223 
224                 if ( ret == null )
225                 {
226                     if ( !Referenznummer10.checkReferenznummer10( num ) )
227                     {
228                         pos.setErrorIndex( startIndex );
229                         ret = null;
230                     }
231                     else
232                     {
233                         pos.setIndex( i );
234                         ret = new Referenznummer10( num );
235                         getCache().put( key, ret );
236                     }
237                 }
238                 else
239                 {
240                     pos.setIndex( i );
241                 }
242             }
243             else
244             {
245                 pos.setErrorIndex( startIndex );
246             }
247         }
248 
249         return ret;
250     }
251 
252     /**
253      * Parses text from the beginning of the given string to produce a {@code Referenznummer10}.
254      * <p>Unlike the {@link #parse(String, ParsePosition)} method this method throws a {@code ParseException} if
255      * {@code referenceCode} cannot be parsed or is of invalid length.</p>
256      *
257      * @param referenceCode A Referenznummer10 in either electronic or letter format.
258      *
259      * @return The parsed value.
260      *
261      * @throws NullPointerException if {@code referenceCode} is {@code null}.
262      * @throws ParseException if the parse fails or {@code referenceCode} is of invalid length.
263      */
264     public static Referenznummer10 parse( final String referenceCode ) throws ParseException
265     {
266         if ( referenceCode == null )
267         {
268             throw new NullPointerException( "referenceCode" );
269         }
270 
271         Referenznummer10 ref = (Referenznummer10) getCache().get( referenceCode );
272 
273         if ( ref == null )
274         {
275             final ParsePosition pos = new ParsePosition( 0 );
276             ref = Referenznummer10.parse( referenceCode, pos );
277             if ( ref == null || pos.getErrorIndex() != -1 || pos.getIndex() < referenceCode.length() )
278             {
279                 throw new ParseException( referenceCode,
280                                           pos.getErrorIndex() != -1 ? pos.getErrorIndex() : pos.getIndex() );
281 
282             }
283             else
284             {
285                 getCache().put( referenceCode, ref );
286             }
287         }
288 
289         return ref;
290     }
291 
292     /**
293      * Returns an instance for the Referenznummer10 identified by the given number.
294      *
295      * @param referenceCode A number identifying a Referenznummer10.
296      *
297      * @return An instance for {@code referenceCode}.
298      *
299      * @throws NullPointerException if {@code referenceCode} is {@code null}.
300      * @throws IllegalArgumentException if {@code referenceCode} is negative, zero or greater than 9999999999.
301      *
302      * @see #checkReferenznummer10(Number)
303      */
304     public static Referenznummer10 valueOf( final Number referenceCode )
305     {
306         if ( referenceCode == null )
307         {
308             throw new NullPointerException( "referenceCode" );
309         }
310 
311         final String key = referenceCode.toString();
312         Referenznummer10 ret = (Referenznummer10) getCache().get( key );
313 
314         if ( ret == null )
315         {
316             ret = new Referenznummer10( referenceCode );
317             getCache().put( key, ret );
318         }
319 
320         return ret;
321     }
322 
323     /**
324      * Parses text from the beginning of the given string to produce a {@code Referenznummer10}.
325      * <p>Unlike the {@link #parse(String)} method this method throws an {@code IllegalArgumentException} if
326      * {@code referenceCode} cannot be parsed or is of invalid length.</p>
327      *
328      * @param referenceCode A Referenznummer10 in either electronic or letter format.
329      *
330      * @return The parsed value.
331      *
332      * @throws NullPointerException if {@code referenceCode} is {@code null}.
333      * @throws IllegalArgumentException if the parse fails or {@code referenceCode} is of invalid length.
334      */
335     public static Referenznummer10 valueOf( final String referenceCode )
336     {
337         try
338         {
339             return Referenznummer10.parse( referenceCode );
340         }
341         catch ( final ParseException e )
342         {
343             throw (IllegalArgumentException) new IllegalArgumentException( referenceCode ).initCause( e );
344         }
345     }
346 
347     /**
348      * Checks a given number to conform to a Referenznummer10.
349      *
350      * @param referenceCode The number to check.
351      *
352      * @return {@code true} if {@code referenceCode} is a valid Referenznummer10; {@code false} if not.
353      */
354     public static boolean checkReferenznummer10( final Number referenceCode )
355     {
356         boolean valid = referenceCode != null;
357 
358         if ( valid )
359         {
360             final long num = referenceCode.longValue();
361             valid = num >= 0L && num < 10000000000L;
362         }
363 
364         return valid;
365     }
366 
367     /**
368      * Returns this Referenznummer10 as an int value.
369      *
370      * @return This Referenznummer10 as an int value.
371      */
372     public int intValue()
373     {
374         return (int) this.ref;
375     }
376 
377     /**
378      * Returns this Referenznummer10 as a long value.
379      *
380      * @return This Referenznummer10 as a long value.
381      */
382     public long longValue()
383     {
384         return this.ref;
385     }
386 
387     /**
388      * Returns this Referenznummer10 as a float value.
389      *
390      * @return This Referenznummer10 as a float value.
391      */
392     public float floatValue()
393     {
394         return this.ref;
395     }
396 
397     /**
398      * Returns this Referenznummer10 as a double value.
399      *
400      * @return This Referenznummer10 as a double value.
401      */
402     public double doubleValue()
403     {
404         return this.ref;
405     }
406 
407     /**
408      * Formats a Referenznummer10 and appends the resulting text to the given string buffer.
409      *
410      * @param style The style to use ({@code ELECTRONIC_FORMAT} or {@code LETTER_FORMAT}).
411      * @param toAppendTo The buffer to which the formatted text is to be appended.
412      *
413      * @return The value passed in as {@code toAppendTo}.
414      *
415      * @throws NullPointerException if {@code toAppendTo} is {@code null}.
416      * @throws IllegalArgumentException if {@code style} is neither {@code ELECTRONIC_FORMAT} nor {@code LETTER_FORMAT}.
417      *
418      * @see #ELECTRONIC_FORMAT
419      * @see #LETTER_FORMAT
420      */
421     public StringBuffer format( final int style, final StringBuffer toAppendTo )
422     {
423         if ( toAppendTo == null )
424         {
425             throw new NullPointerException( "toAppendTo" );
426         }
427         if ( style != Referenznummer10.ELECTRONIC_FORMAT && style != Referenznummer10.LETTER_FORMAT )
428         {
429             throw new IllegalArgumentException( Integer.toString( style ) );
430         }
431 
432         if ( this.ref == 0L )
433         {
434             toAppendTo.append( '0' );
435         }
436         else
437         {
438             final int[] digits = Referenznummer10.toDigits( this.ref );
439             for ( int i = digits.length - 1, lastDigit = 0; i >= 0; i-- )
440             {
441                 if ( digits[i] != 0 || lastDigit > 0 )
442                 {
443                     toAppendTo.append( digits[i] );
444                     lastDigit++;
445                 }
446 
447                 if ( style == Referenznummer10.LETTER_FORMAT && ( lastDigit == 3 || lastDigit == 6 || lastDigit == 9 ) )
448                 {
449                     toAppendTo.append( ' ' );
450                 }
451             }
452         }
453 
454         return toAppendTo;
455     }
456 
457     /**
458      * Formats a Referenznummer10 to produce a string. Same as
459      * <blockquote>
460      * {@link #format(int, StringBuffer) format<code>(style, new StringBuffer()).toString()</code>}
461      * </blockquote>
462      *
463      * @param style The style to use ({@code ELECTRONIC_FORMAT} or {@code LETTER_FORMAT}).
464      *
465      * @return The formatted string.
466      *
467      * @throws IllegalArgumentException if {@code style} is neither {@code ELECTRONIC_FORMAT} nor {@code LETTER_FORMAT}.
468      *
469      * @see #ELECTRONIC_FORMAT
470      * @see #LETTER_FORMAT
471      */
472     public String format( final int style )
473     {
474         return this.format( style, new StringBuffer() ).toString();
475     }
476 
477     /**
478      * Formats a Referenznummer10 to produce a string. Same as
479      * <blockquote>
480      * {@link #format(int) referenznummer10.format(ELECTRONIC_FORMAT)}
481      * </blockquote>
482      *
483      * @param referenznummer10 The {@code Referenznummer10} instance to format.
484      *
485      * @return The formatted string.
486      *
487      * @throws NullPointerException if {@code referenznummer10} is {@code null}.
488      */
489     public static String toString( final Referenznummer10 referenznummer10 )
490     {
491         if ( referenznummer10 == null )
492         {
493             throw new NullPointerException( "referenznummer10" );
494         }
495 
496         return referenznummer10.format( ELECTRONIC_FORMAT );
497     }
498 
499     /**
500      * Creates an array holding the digits of {@code number}.
501      *
502      * @param number The number to return the digits for.
503      *
504      * @return An array holding the digits of {@code number}.
505      */
506     private static int[] toDigits( final long number )
507     {
508         int i;
509         int j;
510         long subst;
511         final int[] ret = new int[ MAX_DIGITS ];
512 
513         for ( i = MAX_DIGITS - 1; i >= 0; i-- )
514         {
515             for ( j = i + 1, subst = 0L; j < MAX_DIGITS; j++ )
516             {
517                 subst += ret[j] * EXP10[j];
518             }
519             ret[i] = (int) Math.floor( ( number - subst ) / EXP10[i] );
520         }
521 
522         return ret;
523     }
524 
525     /**
526      * Creates a string representing the properties of the instance.
527      *
528      * @return A string representing the properties of the instance.
529      */
530     private String internalString()
531     {
532         return new StringBuffer( 500 ).append( "{referenceNumber=" ).append( this.ref ).append( '}' ).toString();
533     }
534 
535     /**
536      * Gets the current cache instance.
537      *
538      * @return Current cache instance.
539      */
540     private static Map getCache()
541     {
542         Map cache = (Map) cacheReference.get();
543         if ( cache == null )
544         {
545             cache = Collections.synchronizedMap( new HashMap( 1024 ) );
546             cacheReference = new SoftReference( cache );
547         }
548 
549         return cache;
550     }
551 
552     /**
553      * Compares this object with the specified object for order. Returns a negative integer, zero, or a positive integer
554      * as this object is less than, equal to, or greater than the specified object.<p>
555      *
556      * @param o The Object to be compared.
557      * @return  A negative integer, zero, or a positive integer as this object is less than, equal to, or greater than
558      * the specified object.
559      *
560      * @throws NullPointerException if {@code o} is {@code null}.
561      * @throws ClassCastException if the specified object's type prevents it from being compared to this Object.
562      */
563     public int compareTo( final Object o )
564     {
565         if ( o == null )
566         {
567             throw new NullPointerException( "o" );
568         }
569         if ( !( o instanceof Referenznummer10 ) )
570         {
571             throw new ClassCastException( o.getClass().getName() );
572         }
573 
574         int result = 0;
575         final Referenznummer10 that = (Referenznummer10) o;
576 
577         if ( !this.equals( that ) )
578         {
579             result = this.ref > that.ref ? 1 : -1;
580         }
581 
582         return result;
583     }
584 
585     /**
586      * Indicates whether some other object is equal to this one.
587      *
588      * @param o The reference object with which to compare.
589      *
590      * @return {@code true} if this object is the same as {@code o}; {@code false} otherwise.
591      */
592     public boolean equals( final Object o )
593     {
594         boolean equal = o == this;
595 
596         if ( !equal && o instanceof Referenznummer10 )
597         {
598             equal = this.ref == ( (Referenznummer10) o ).ref;
599         }
600 
601         return equal;
602     }
603 
604     /**
605      * Returns a hash code value for this object.
606      *
607      * @return A hash code value for this object.
608      */
609     public int hashCode()
610     {
611         return (int) ( this.ref ^ ( this.ref >>> 32 ) );
612     }
613 
614     /**
615      * Returns a string representation of the object.
616      *
617      * @return A string representation of the object.
618      */
619     public String toString()
620     {
621         return super.toString() + this.internalString();
622     }
623 
624 }