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.io.Serializable; 024import java.lang.ref.Reference; 025import java.lang.ref.SoftReference; 026import java.text.ParseException; 027import java.text.ParsePosition; 028import java.util.Collections; 029import java.util.HashMap; 030import java.util.Map; 031 032/** 033 * Alpha numeric text with a maximum length of twenty seven characters. 034 * <p>Data type for the alpha-numeric DTAUS alphabet. For further information 035 * see the <a href="../../../doc-files/Anlage3_Datenformate_V2.7.pdf"> 036 * Spezifikation der Datenformate</a>. An updated version of the document may be found at 037 * <a href="http://www.ebics.de">EBICS</a>.</p> 038 * 039 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a> 040 * @version $JDTAUS: AlphaNumericText27.java 8860 2014-01-10 17:09:37Z schulte $ 041 */ 042public final class AlphaNumericText27 implements CharSequence, Comparable, Serializable 043{ 044 045 /** Serial version UID for backwards compatibility with 1.0.x classes. */ 046 private static final long serialVersionUID = -5231830564347967536L; 047 048 /** Used to cache instances. */ 049 private static volatile Reference cacheReference = new SoftReference( null ); 050 051 /** Constant for the maximum length allowed for an instance. */ 052 public static final int MAX_LENGTH = 27; 053 054 /** 055 * The alpha-numeric text. 056 * @serial 057 */ 058 private String text; 059 060 /** 061 * Flag indicating that {@code text} contains no text. 062 * @serial 063 */ 064 private boolean empty; 065 066 /** 067 * Creates a new {@code AlphaNumericText27} instance holding {@code text}. 068 * 069 * @param text The text for the instance. 070 * 071 * @throws NullPointerException if {@code text} is {@code null}. 072 * @throws IllegalArgumentException if the length of {@code text} is greater than {@code MAX_LENGTH}. 073 * 074 * @see #parse(String, ParsePosition) 075 */ 076 private AlphaNumericText27( final String text ) 077 { 078 if ( text == null ) 079 { 080 throw new NullPointerException( "text" ); 081 } 082 if ( text.length() > MAX_LENGTH ) 083 { 084 throw new IllegalArgumentException( text ); 085 } 086 087 this.text = text; 088 this.empty = text.trim().length() == 0; 089 } 090 091 /** 092 * Parses text from a string to produce an {@code AlphaNumericText27} instance. 093 * <p>The method attempts to parse text starting at the index given by {@code pos}. If parsing succeeds, then the 094 * index of {@code pos} is updated to the index after the last character used (parsing does not necessarily use all 095 * characters up to the end of the string), and the parsed value is returned. The updated {@code pos} can be used to 096 * indicate the starting point for the next call to this method.</p> 097 * 098 * @param text A string to parse alpha numeric characters from. 099 * @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}