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 }