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.io.Serializable;
24  import java.lang.ref.Reference;
25  import java.lang.ref.SoftReference;
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   * Alpha numeric text with a maximum length of twenty seven characters.
34   * <p>Data type for the alpha-numeric DTAUS alphabet. For further information
35   * see the <a href="../../../doc-files/Anlage3_Datenformate_V2.7.pdf">
36   * Spezifikation der Datenformate</a>. An updated version of the document may be found at
37   * <a href="http://www.ebics.de">EBICS</a>.</p>
38   *
39   * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
40   * @version $JDTAUS: AlphaNumericText27.java 8860 2014-01-10 17:09:37Z schulte $
41   */
42  public final class AlphaNumericText27 implements CharSequence, Comparable, Serializable
43  {
44  
45      /** Serial version UID for backwards compatibility with 1.0.x classes. */
46      private static final long serialVersionUID = -5231830564347967536L;
47  
48      /** Used to cache instances. */
49      private static volatile Reference cacheReference = new SoftReference( null );
50  
51      /** Constant for the maximum length allowed for an instance. */
52      public static final int MAX_LENGTH = 27;
53  
54      /**
55       * The alpha-numeric text.
56       * @serial
57       */
58      private String text;
59  
60      /**
61       * Flag indicating that {@code text} contains no text.
62       * @serial
63       */
64      private boolean empty;
65  
66      /**
67       * Creates a new {@code AlphaNumericText27} instance holding {@code text}.
68       *
69       * @param text The text for the instance.
70       *
71       * @throws NullPointerException if {@code text} is {@code null}.
72       * @throws IllegalArgumentException if the length of {@code text} is greater than {@code MAX_LENGTH}.
73       *
74       * @see #parse(String, ParsePosition)
75       */
76      private AlphaNumericText27( final String text )
77      {
78          if ( text == null )
79          {
80              throw new NullPointerException( "text" );
81          }
82          if ( text.length() > MAX_LENGTH )
83          {
84              throw new IllegalArgumentException( text );
85          }
86  
87          this.text = text;
88          this.empty = text.trim().length() == 0;
89      }
90  
91      /**
92       * Parses text from a string to produce an {@code AlphaNumericText27} instance.
93       * <p>The method attempts to parse text starting at the index given by {@code pos}. If parsing succeeds, then the
94       * index of {@code pos} is updated to the index after the last character used (parsing does not necessarily use all
95       * characters up to the end of the string), and the parsed value is returned. The updated {@code pos} can be used to
96       * indicate the starting point for the next call to this method.</p>
97       *
98       * @param text A string to parse alpha numeric characters from.
99       * @param pos A {@code ParsePosition} object with index and error index information as described above.
100      *
101      * @return The parsed value, or {@code null} if the parse fails.
102      *
103      * @throws NullPointerException if either {@code text} or {@code pos} is {@code null}.
104      */
105     public static AlphaNumericText27 parse( final String text, final ParsePosition pos )
106     {
107         if ( text == null )
108         {
109             throw new NullPointerException( "text" );
110         }
111         if ( pos == null )
112         {
113             throw new NullPointerException( "pos" );
114         }
115 
116         int i;
117         boolean valid = true;
118         AlphaNumericText27 ret = null;
119         final int beginIndex = pos.getIndex();
120         final int len = text.length();
121 
122         for ( i = beginIndex; i < beginIndex + MAX_LENGTH && i < len; i++ )
123         {
124             if ( !AlphaNumericText27.checkAlphaNumeric( text.charAt( i ) ) )
125             {
126                 pos.setErrorIndex( i );
127                 valid = false;
128                 break;
129             }
130         }
131 
132         if ( valid )
133         {
134             pos.setIndex( i );
135             ret = new AlphaNumericText27( text.substring( beginIndex, i ) );
136         }
137 
138         return ret;
139     }
140 
141     /**
142      * Parses text from the beginning of the given string to produce an {@code AlphaNumericText27} instance.
143      * <p>Unlike the {@link #parse(String, ParsePosition)} method this method throws a {@code ParseException} if
144      * {@code text} cannot be parsed or is of invalid length.</p>
145      *
146      * @param text A string to parse alpha numeric characters from.
147      *
148      * @return The parsed value.
149      *
150      * @throws NullPointerException if {@code text} is {@code null}.
151      * @throws ParseException if the parse fails or the length of {@code text} is greater than {@code 27}.
152      */
153     public static AlphaNumericText27 parse( final String text ) throws ParseException
154     {
155         if ( text == null )
156         {
157             throw new NullPointerException( "text" );
158         }
159 
160         AlphaNumericText27 txt = (AlphaNumericText27) getCache().get( text );
161 
162         if ( txt == null )
163         {
164             final ParsePosition pos = new ParsePosition( 0 );
165             txt = AlphaNumericText27.parse( text, pos );
166 
167             if ( txt == null || pos.getErrorIndex() != -1 || pos.getIndex() < text.length() )
168             {
169                 throw new ParseException( text, pos.getErrorIndex() != -1 ? pos.getErrorIndex() : pos.getIndex() );
170             }
171             else
172             {
173                 getCache().put( text, txt );
174             }
175         }
176 
177         return txt;
178     }
179 
180     /**
181      * Parses text from the beginning of the given string to produce an {@code AlphaNumericText27} instance.
182      * <p>Unlike the {@link #parse(String)} method this method throws an {@code IllegalArgumentException} if
183      * {@code text} cannot be parsed or is of invalid length.</p>
184      *
185      * @param text A formatted string representation of an {@code AlphaNumericText27} instance.
186      *
187      * @return The parsed value.
188      *
189      * @throws NullPointerException if {@code text} is {@code null}.
190      * @throws IllegalArgumentException if the parse fails or the length of {@code text} is greater than {@code 27}.
191      */
192     public static AlphaNumericText27 valueOf( final String text )
193     {
194         try
195         {
196             return AlphaNumericText27.parse( text );
197         }
198         catch ( final ParseException e )
199         {
200             throw (IllegalArgumentException) new IllegalArgumentException( text ).initCause( e );
201         }
202     }
203 
204     /**
205      * Formats alpha-numeric characters and appends the resulting text to the given string buffer.
206      *
207      * @param toAppendTo The buffer to which the formatted text is to be appended.
208      *
209      * @return The value passed in as {@code toAppendTo}.
210      *
211      * @throws NullPointerException if {@code toAppendTo} is {@code null}.
212      */
213     public StringBuffer format( final StringBuffer toAppendTo )
214     {
215         if ( toAppendTo == null )
216         {
217             throw new NullPointerException( "toAppendTo" );
218         }
219 
220         return toAppendTo.append( this.text );
221     }
222 
223     /**
224      * Formats alpha-numeric characters to produce a string. Same as
225      * <blockquote>
226      * {@link #format(StringBuffer) format<code>(new StringBuffer()).toString()</code>}
227      * </blockquote>
228      *
229      * @return The formatted string.
230      */
231     public String format()
232     {
233         return this.text;
234     }
235 
236     /**
237      * Formats alpha-numeric characters to produce a string. Same as
238      * <blockquote>
239      * {@link #format() alphaNumericText27.format()}
240      * </blockquote>
241      *
242      * @param alphaNumericText27 The {@code AlphaNumericText27} instance to format.
243      *
244      * @return The formatted string.
245      *
246      * @throws NullPointerException if {@code alphaNumericText27} is {@code null}.
247      */
248     public static String toString( final AlphaNumericText27 alphaNumericText27 )
249     {
250         if ( alphaNumericText27 == null )
251         {
252             throw new NullPointerException( "alphaNumericText27" );
253         }
254 
255         return alphaNumericText27.format();
256     }
257 
258     /**
259      * Checks a given character to belong to the alpha-numeric alphabet.
260      *
261      * @param c The character to check.
262      *
263      * @return {@code true} if {@code c} is a character of the alpha-numeric DTAUS alphabet; {@code false} if not.
264      */
265     public static boolean checkAlphaNumeric( final char c )
266     {
267         return ( c >= 'A' && c <= 'Z' ) || ( c >= '0' && c <= '9' ) ||
268                ( c == '.' || c == '+' || c == '*' || c == '$' || c == ' ' || c == ',' || c == '&' || c == '-' ||
269                  c == '/' || c == '%' || c == 'Ä' || c == 'Ö' || c == 'Ü' || c == 'ß' );
270 
271     }
272 
273     /**
274      * Normalizes text to conform to the alpha-numeric alphabet.
275      * <p>This method converts lower case letters to upper case letters and replaces all illegal characters with spaces.
276      * It will return the unchanged text if for every given character {@code C} the method {@code checkAlphaNumeric(C)}
277      * returns {@code true}.</p>
278      * <p>Note that code like
279      * <blockquote><code>
280      * AlphaNumericText27.parse(AlphaNumericText27.normalize(getSomething()));
281      * </code></blockquote>
282      * may be dangerous if {@code getSomething()} may provide unchecked or otherwise invalid data which will get
283      * converted to valid data by this method.</p>
284      * <p>Also note that
285      * <blockquote><code>
286      * AlphaNumericText27.normalize(<i>something</i>).length() == <i>something</i>.length();
287      * </code></blockquote>
288      * is always {@code true} although
289      * <blockquote><code>
290      * AlphaNumericText27.normalize(<i>something</i>).equals(<i>something</i>);
291      * </code></blockquote> may be {@code false}.</p>
292      * <p>It is recommended to always check for changes before proceeding with the data this method returns.
293      * For example:
294      * <blockquote><pre>
295      * something = getSomething();
296      * normalized = AlphaNumericText27.normalize(something);
297      * if(!something.equals(normalized)) {
298      *
299      *     <i>e.g. check the normalized value, log a warning, display the
300      *     normalized value to the user for confirmation</i>
301      *
302      * }</pre></blockquote></p>
303      *
304      * @param text The text to normalize.
305      *
306      * @return {@code text} normalized to conform to the alpha-numeric alphabet.
307      *
308      * @throws NullPointerException if {@code text} is {@code null}.
309      */
310     public static String normalize( final String text )
311     {
312         if ( text == null )
313         {
314             throw new NullPointerException( "text" );
315         }
316 
317         final char[] ret = text.toCharArray();
318 
319         for ( int i = ret.length - 1; i >= 0; i-- )
320         {
321             if ( Character.isLowerCase( ret[i] ) )
322             {
323                 ret[i] = Character.toUpperCase( ret[i] );
324             }
325 
326             if ( !AlphaNumericText27.checkAlphaNumeric( ret[i] ) )
327             {
328                 ret[i] = ' ';
329             }
330         }
331 
332         return new String( ret );
333     }
334 
335     /**
336      * Flag indicating that the instance contains no text.
337      *
338      * @return {@code true} if the instance contains no text but just whitespace characters; {@code false} if the
339      * instance contains text.
340      */
341     public boolean isEmpty()
342     {
343         return this.empty;
344     }
345 
346     /**
347      * Gets the current cache instance.
348      *
349      * @return Current cache instance.
350      */
351     private static Map getCache()
352     {
353         Map cache = (Map) cacheReference.get();
354         if ( cache == null )
355         {
356             cache = Collections.synchronizedMap( new HashMap( 1024 ) );
357             cacheReference = new SoftReference( cache );
358         }
359 
360         return cache;
361     }
362 
363     /**
364      * Returns the length of this character sequence.  The length is the number of 16-bit {@code char}s in the sequence.
365      *
366      * @return The number of {@code char}s in this sequence.
367      */
368     public int length()
369     {
370         return this.text.length();
371     }
372 
373     /**
374      * Returns the {@code char} value at the specified index.
375      * <p>An index ranges from zero to {@code length() - 1}. The first {@code char} value of the sequence is at index
376      * zero, the next at index one, and so on, as for array indexing.</p>
377      *
378      * @param index The index of the {@code char} value to be returned.
379      *
380      * @return The specified {@code char} value.
381      *
382      * @throws IndexOutOfBoundsException if {@code index} is negative or not less than {@code length()}.
383      */
384     public char charAt( final int index )
385     {
386         return this.text.charAt( index );
387     }
388 
389     /**
390      * Returns a new {@code CharSequence} that is a subsequence of this sequence.
391      * <p>The subsequence starts with the {@code char} value at the specified index and ends with the {@code char} value
392      * at index {@code end - 1}. The length (in {@code char}s) of the returned sequence is {@code end - start}, so if
393      * {@code start == end} then an empty sequence is returned.</p>
394      *
395      * @param start The start index, inclusive.
396      * @param end The end index, exclusive.
397      *
398      * @return The specified subsequence.
399      *
400      * @throws  IndexOutOfBoundsException if {@code start} or {@code end} are negative, if {@code end} is greater than
401      * {@code length()}, or if {@code start} is greater than {@code end}.
402      */
403     public CharSequence subSequence( final int start, final int end )
404     {
405         return this.text.subSequence( start, end );
406     }
407 
408     /**
409      * Returns a string containing the characters in this sequence in the same order as this sequence. The length of the
410      * string will be the length of this sequence.
411      *
412      * @return A string consisting of exactly this sequence of characters.
413      */
414     public String toString()
415     {
416         return this.text;
417     }
418 
419     /**
420      * Compares this object with the specified object for order.  Returns a negative integer, zero, or a positive
421      * integer as this object is less than, equal to, or greater than the specified object.
422      *
423      * @param o The Object to be compared.
424      * @return A negative integer, zero, or a positive integer as this object is less than, equal to, or greater than
425      * the specified object.
426      *
427      * @throws NullPointerException if {@code o} is {@code null}.
428      * @throws ClassCastException if the specified object's type prevents it from being compared to this Object.
429      */
430     public int compareTo( final Object o )
431     {
432         if ( o == null )
433         {
434             throw new NullPointerException( "o" );
435         }
436         if ( !( o instanceof AlphaNumericText27 ) )
437         {
438             throw new ClassCastException( o.getClass().getName() );
439         }
440 
441         final AlphaNumericText27 that = (AlphaNumericText27) o;
442 
443         int result = 0;
444 
445         if ( !this.equals( o ) )
446         {
447             if ( this.text == null )
448             {
449                 result = that.text == null ? 0 : -1;
450             }
451             else
452             {
453                 result = that.text == null ? 1 : this.text.compareTo( that.text );
454             }
455         }
456 
457         return result;
458     }
459 
460     /**
461      * Indicates whether some other object is equal to this one.
462      *
463      * @param o The reference object with which to compare.
464      *
465      * @return {@code true} if this object is the same as {@code o}; {@code false} otherwise.
466      */
467     public boolean equals( final Object o )
468     {
469         boolean ret = o == this;
470 
471         if ( !ret && o instanceof AlphaNumericText27 )
472         {
473             final AlphaNumericText27 that = (AlphaNumericText27) o;
474             ret = this.text == null ? that.text == null : this.text.equals( that.text );
475         }
476 
477         return ret;
478     }
479 
480     /**
481      * Returns a hash code value for this object.
482      *
483      * @return A hash code value for this object.
484      */
485     public int hashCode()
486     {
487         return this.text == null ? 0 : this.text.hashCode();
488     }
489 
490 }