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 }