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 | } |