001/*
002 *  jDTAUS Banking API
003 *  Copyright (C) 2005 Christian Schulte
004 *  <cs@schulte.it>
005 *
006 *  This library is free software; you can redistribute it and/or
007 *  modify it under the terms of the GNU Lesser General Public
008 *  License as published by the Free Software Foundation; either
009 *  version 2.1 of the License, or any later version.
010 *
011 *  This library is distributed in the hope that it will be useful,
012 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
013 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 *  Lesser General Public License for more details.
015 *
016 *  You should have received a copy of the GNU Lesser General Public
017 *  License along with this library; if not, write to the Free Software
018 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
019 *
020 */
021package org.jdtaus.banking;
022
023import java.lang.ref.Reference;
024import java.lang.ref.SoftReference;
025import java.text.DecimalFormat;
026import java.text.ParseException;
027import java.text.ParsePosition;
028import java.util.Collections;
029import java.util.HashMap;
030import java.util.Map;
031
032/**
033 * Unique entity identifier.
034 * <p>A Referenznummer10 is a positive integer with a maximum of ten digits.</p>
035 *
036 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
037 * @version $JDTAUS: Referenznummer10.java 8661 2012-09-27 11:29:58Z schulte $
038 */
039public final class Referenznummer10 extends Number implements Comparable
040{
041
042    /**
043     * Constant for the electronic format of a Referenznummer10.
044     * <p>The electronic format of a Referenznummer10 is a ten digit number with leading zeros omitted (e.g. 6789).</p>
045     */
046    public static final int ELECTRONIC_FORMAT = 5001;
047
048    /**
049     * Constant for the letter format of a Referenznummer10.
050     * <p>The letter format of a Referenznummer10 is a ten digit number with leading zeros omitted separated by spaces
051     * between the first three digits and the second three digits, the second three digits and the third three digits,
052     * and between the third three digits and the last digit (e.g. 123 456 789 0).</p>
053     */
054    public static final int LETTER_FORMAT = 5002;
055
056    /** Maximum number of digits of a Referenznummer10. */
057    public static final int MAX_DIGITS = 10;
058
059    /** Maximum number of characters of a Referenznummer10. */
060    public static final int MAX_CHARACTERS = 13;
061
062    /** {@code 10^0..10^9}. */
063    private static final double[] EXP10 =
064    {
065        1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000,
066        1000000000
067    };
068
069    /** Serial version UID for backwards compatibility with 1.0.x classes. */
070    private static final long serialVersionUID = -72660089907415650L;
071
072    /** Used to cache instances. */
073    private static volatile Reference cacheReference = new SoftReference( null );
074
075    /**
076     * Reference code.
077     * @serial
078     */
079    private long ref;
080
081    /**
082     * Creates a new {@code Referenznummer10} instance.
083     *
084     * @param referenceCode The long to create an instance from.
085     *
086     * @throws IllegalArgumentException if {@code referenceCode} is negative, zero or greater than 9999999999.
087     *
088     * @see #checkReferenznummer10(Number)
089     */
090    private Referenznummer10( final Number referenceCode )
091    {
092        if ( !Referenznummer10.checkReferenznummer10( referenceCode ) )
093        {
094            throw new IllegalArgumentException( referenceCode.toString() );
095        }
096
097        this.ref = referenceCode.longValue();
098    }
099
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}