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