001/* 002 * jDTAUS Banking Utilities 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.util.swing; 022 023import java.beans.PropertyChangeEvent; 024import java.beans.PropertyChangeListener; 025import java.text.DecimalFormat; 026import java.text.NumberFormat; 027import java.text.ParseException; 028import java.util.Locale; 029import javax.swing.JFormattedTextField; 030import javax.swing.JFormattedTextField.AbstractFormatter; 031import javax.swing.SwingUtilities; 032import javax.swing.text.AttributeSet; 033import javax.swing.text.BadLocationException; 034import javax.swing.text.DocumentFilter; 035import javax.swing.text.DocumentFilter.FilterBypass; 036import org.jdtaus.banking.Bankleitzahl; 037import org.jdtaus.banking.BankleitzahlExpirationException; 038import org.jdtaus.banking.BankleitzahlInfo; 039import org.jdtaus.banking.BankleitzahlenVerzeichnis; 040import org.jdtaus.banking.messages.BankleitzahlExpirationMessage; 041import org.jdtaus.banking.messages.BankleitzahlReplacementMessage; 042import org.jdtaus.banking.messages.UnknownBankleitzahlMessage; 043import org.jdtaus.core.container.ContainerFactory; 044import org.jdtaus.core.container.PropertyException; 045 046/** 047 * {@code JFormattedTextField} supporting the {@code Bankleitzahl} type. 048 * <p>This textfield uses the {@link Bankleitzahl} type for parsing and formatting. An empty string value is treated as 049 * {@code null}. Property {@code format} controls formatting and takes one of the format constants defined in class 050 * {@code Bankleitzahl}. By default the {@code ELECTRONIC_FORMAT} is used. The {@code validating} flag controls 051 * validation of values entered into the textfield. If {@code true} (default), a {@code DocumentFilter} is registered 052 * with the textfield disallowing invalid values, that is, values which are not {@code null} and not empty strings and 053 * for which the {@link Bankleitzahl#parse(String)} method throws a {@code ParseException}. The field's tooltip 054 * text is updated with information about the field's value.</p> 055 * 056 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a> 057 * @version $JDTAUS: BankleitzahlTextField.java 8864 2014-01-10 17:13:30Z schulte $ 058 */ 059public final class BankleitzahlTextField extends JFormattedTextField 060{ 061 062 /** Serial version UID for backwards compatibility with 1.1.x classes. */ 063 private static final long serialVersionUID = -5461742987164339047L; 064 065 /** 066 * The constant of the format to use when formatting Bankleitzahl instances. 067 * @serial 068 */ 069 private Integer format; 070 071 /** 072 * Flag indicating if validation is performed. 073 * @serial 074 */ 075 private Boolean validating; 076 077 /** Creates a new default {@code BankleitzahlTextField} instance. */ 078 public BankleitzahlTextField() 079 { 080 super(); 081 this.assertValidProperties(); 082 this.setColumns( Bankleitzahl.MAX_CHARACTERS ); 083 this.setFormatterFactory( new AbstractFormatterFactory() 084 { 085 086 public AbstractFormatter getFormatter( final JFormattedTextField ftf ) 087 { 088 return new AbstractFormatter() 089 { 090 091 public Object stringToValue( final String text ) throws ParseException 092 { 093 Object value = null; 094 095 if ( text != null && text.trim().length() > 0 ) 096 { 097 value = Bankleitzahl.parse( text ); 098 } 099 100 return value; 101 } 102 103 public String valueToString( final Object value ) throws ParseException 104 { 105 String ret = null; 106 107 if ( value instanceof Bankleitzahl ) 108 { 109 final Bankleitzahl blz = (Bankleitzahl) value; 110 ret = blz.format( getFormat() ); 111 } 112 113 return ret; 114 } 115 116 protected DocumentFilter getDocumentFilter() 117 { 118 return new DocumentFilter() 119 { 120 121 public void insertString( final FilterBypass fb, final int o, String s, 122 final AttributeSet a ) throws BadLocationException 123 { 124 if ( isValidating() ) 125 { 126 final StringBuffer b = new StringBuffer( fb.getDocument().getLength() + s.length() ); 127 b.append( fb.getDocument().getText( 0, fb.getDocument().getLength() ) ); 128 b.insert( o, s ); 129 130 try 131 { 132 Bankleitzahl.parse( b.toString() ); 133 } 134 catch ( ParseException e ) 135 { 136 invalidEdit(); 137 return; 138 } 139 } 140 141 super.insertString( fb, o, s, a ); 142 } 143 144 public void replace( final FilterBypass fb, final int o, final int l, String s, 145 final AttributeSet a ) throws BadLocationException 146 { 147 if ( isValidating() ) 148 { 149 final StringBuffer b = new StringBuffer( 150 fb.getDocument().getText( 0, fb.getDocument().getLength() ) ); 151 152 b.delete( o, o + l ); 153 154 if ( s != null ) 155 { 156 b.insert( o, s ); 157 } 158 159 try 160 { 161 Bankleitzahl.parse( b.toString() ); 162 } 163 catch ( ParseException e ) 164 { 165 invalidEdit(); 166 return; 167 } 168 } 169 170 super.replace( fb, o, l, s, a ); 171 } 172 173 }; 174 } 175 176 }; 177 } 178 179 } ); 180 181 this.addPropertyChangeListener( "value", new PropertyChangeListener() 182 { 183 184 public void propertyChange( final PropertyChangeEvent evt ) 185 { 186 new Thread() 187 { 188 189 public void run() 190 { 191 updateTooltip( (Bankleitzahl) evt.getNewValue() ); 192 } 193 194 }.start(); 195 } 196 197 } ); 198 } 199 200 /** 201 * Gets the last valid {@code Bankleitzahl}. 202 * 203 * @return the last valid {@code Bankleitzahl} or {@code null}. 204 */ 205 public Bankleitzahl getBankleitzahl() 206 { 207 return (Bankleitzahl) this.getValue(); 208 } 209 210 /** 211 * Gets the constant of the format used when formatting Bankleitzahl instances. 212 * 213 * @return the constant of the format used when formatting Bankleitzahl instances. 214 * 215 * @see Bankleitzahl#ELECTRONIC_FORMAT 216 * @see Bankleitzahl#LETTER_FORMAT 217 */ 218 public int getFormat() 219 { 220 if ( this.format == null ) 221 { 222 this.format = this.getDefaultFormat(); 223 } 224 225 return this.format.intValue(); 226 } 227 228 /** 229 * Sets the constant of the format to use when formatting Bankleitzahl instances. 230 * 231 * @param value the constant of the format to use when formatting Bankleitzahl instances. 232 * 233 * @throws IllegalArgumentException if {@code format} is neither {@code ELECTRONIC_FORMAT} nor 234 * {@code LETTER_FORMAT}. 235 * 236 * @see Bankleitzahl#ELECTRONIC_FORMAT 237 * @see Bankleitzahl#LETTER_FORMAT 238 */ 239 public void setFormat( final int value ) 240 { 241 if ( value != Bankleitzahl.ELECTRONIC_FORMAT && value != Bankleitzahl.LETTER_FORMAT ) 242 { 243 throw new IllegalArgumentException( Integer.toString( value ) ); 244 } 245 246 this.format = new Integer( value ); 247 } 248 249 /** 250 * Gets the flag indicating if validation is performed. 251 * 252 * @return {@code true} if the fields' value is validated; {@code false} if no validation of the fields' value is 253 * performed. 254 */ 255 public boolean isValidating() 256 { 257 if ( this.validating == null ) 258 { 259 this.validating = this.isDefaultValidating(); 260 } 261 262 return this.validating.booleanValue(); 263 } 264 265 /** 266 * Sets the flag indicating if validation should be performed. 267 * 268 * @param value {@code true} to validate the fields' values; {@code false} to not validate the fields' values. 269 */ 270 public void setValidating( boolean value ) 271 { 272 this.validating = Boolean.valueOf( value ); 273 } 274 275 /** 276 * Updates the component's tooltip to show information available for the value returned by 277 * {@link #getBankleitzahl()}. This method is called whenever a {@code PropertyChangeEvent} for the property with 278 * name {@code value} occurs. 279 */ 280 private void updateTooltip( final Bankleitzahl bankCode ) 281 { 282 final StringBuffer tooltip = new StringBuffer( 200 ); 283 284 if ( bankCode != null ) 285 { 286 tooltip.append( "<html>" ); 287 288 try 289 { 290 final BankleitzahlInfo headOffice = this.getBankleitzahlenVerzeichnis().getHeadOffice( bankCode ); 291 292 if ( headOffice != null ) 293 { 294 tooltip.append( "<b>" ).append( this.getHeadOfficeInfoMessage( this.getLocale() ) ). 295 append( "</b><br>" ); 296 297 this.appendBankleitzahlInfo( headOffice, tooltip ); 298 } 299 else 300 { 301 tooltip.append( new UnknownBankleitzahlMessage( bankCode ).getText( this.getLocale() ) ); 302 } 303 } 304 catch ( final BankleitzahlExpirationException e ) 305 { 306 tooltip.append( new BankleitzahlExpirationMessage( 307 e.getExpiredBankleitzahlInfo() ).getText( this.getLocale() ) ); 308 309 tooltip.append( "<br>" ).append( new BankleitzahlReplacementMessage( 310 e.getReplacingBankleitzahlInfo() ).getText( this.getLocale() ) ); 311 312 tooltip.append( "<br>" ); 313 this.appendBankleitzahlInfo( e.getReplacingBankleitzahlInfo(), tooltip ); 314 } 315 316 tooltip.append( "</html>" ); 317 } 318 319 SwingUtilities.invokeLater( new Runnable() 320 { 321 322 public void run() 323 { 324 setToolTipText( tooltip.length() > 0 ? tooltip.toString() : null ); 325 } 326 327 } ); 328 329 } 330 331 /** 332 * Checks configured properties. 333 * 334 * @throws PropertyException for invalid property values. 335 */ 336 private void assertValidProperties() 337 { 338 if ( this.getFormat() != Bankleitzahl.ELECTRONIC_FORMAT && this.getFormat() != Bankleitzahl.LETTER_FORMAT ) 339 { 340 throw new PropertyException( "format", Integer.toString( this.getFormat() ) ); 341 } 342 } 343 344 /** 345 * Appends the tooltip text for a given {@code BankleitzahlInfo} to a given {@code StringBuffer}. 346 * 347 * @param bankleitzahlInfo The {@code BankleitzahlInfo} instance to append to {@code buf}. 348 * @param buf The {@code StringBuffer} instance to append {@code bankleitzahlInfo} to. 349 * 350 * @return {@code buf} with information about {@code bankleitzahlInfo} appended. 351 * 352 * @throws NullPointerException if {@code bankleitzahlInfo} is {@code null}. 353 */ 354 private StringBuffer appendBankleitzahlInfo( final BankleitzahlInfo bankleitzahlInfo, StringBuffer buf ) 355 { 356 if ( bankleitzahlInfo == null ) 357 { 358 throw new NullPointerException( "bankleitzahlInfo" ); 359 } 360 if ( buf == null ) 361 { 362 buf = new StringBuffer(); 363 } 364 365 final NumberFormat zipFormat = new DecimalFormat( "#####" ); 366 buf.append( "<br>" ).append( bankleitzahlInfo.getName() ); 367 368 if ( bankleitzahlInfo.getDescription() != null && bankleitzahlInfo.getDescription().trim().length() > 0 369 && !bankleitzahlInfo.getName().equals( bankleitzahlInfo.getDescription() ) ) 370 { 371 buf.append( " (" ).append( bankleitzahlInfo.getDescription() ).append( ")" ); 372 } 373 374 buf.append( "<br>" ).append( zipFormat.format( bankleitzahlInfo.getPostalCode() ) ).append( " " ). 375 append( bankleitzahlInfo.getCity() ); 376 377 buf.append( "<br>" ).append( this.getBlzInfoMessage( 378 this.getLocale(), bankleitzahlInfo.getBankCode().format( this.getFormat() ) ) ); 379 380 if ( bankleitzahlInfo.getBic() != null && bankleitzahlInfo.getBic().trim().length() > 0 ) 381 { 382 buf.append( "<br>" ).append( this.getBicInfoMessage( this.getLocale(), bankleitzahlInfo.getBic() ) ); 383 } 384 385 if ( bankleitzahlInfo.isMarkedForDeletion() ) 386 { 387 buf.append( "<br><br><b>" ).append( this.getBankleitzahlMarkedForDeletionInfoMessage( 388 this.getLocale(), this.getBankleitzahlenVerzeichnis().getDateOfExpiration() ) ).append( "</b>" ); 389 390 } 391 392 return buf; 393 } 394 395 //--Dependencies------------------------------------------------------------ 396 397// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausDependencies 398 // This section is managed by jdtaus-container-mojo. 399 400 /** 401 * Gets the configured <code>BankleitzahlenVerzeichnis</code> implementation. 402 * 403 * @return The configured <code>BankleitzahlenVerzeichnis</code> implementation. 404 */ 405 private BankleitzahlenVerzeichnis getBankleitzahlenVerzeichnis() 406 { 407 return (BankleitzahlenVerzeichnis) ContainerFactory.getContainer(). 408 getDependency( this, "BankleitzahlenVerzeichnis" ); 409 410 } 411 412// </editor-fold>//GEN-END:jdtausDependencies 413 414 //------------------------------------------------------------Dependencies-- 415 //--Properties-------------------------------------------------------------- 416 417// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausProperties 418 // This section is managed by jdtaus-container-mojo. 419 420 /** 421 * Gets the value of property <code>defaultValidating</code>. 422 * 423 * @return Default value of the flag indicating if validation should be performed. 424 */ 425 private java.lang.Boolean isDefaultValidating() 426 { 427 return (java.lang.Boolean) ContainerFactory.getContainer(). 428 getProperty( this, "defaultValidating" ); 429 430 } 431 432 /** 433 * Gets the value of property <code>defaultFormat</code>. 434 * 435 * @return Default value of the format to use when formatting Bankleitzahl instances (3001 = electronic format, 3002 letter format). 436 */ 437 private java.lang.Integer getDefaultFormat() 438 { 439 return (java.lang.Integer) ContainerFactory.getContainer(). 440 getProperty( this, "defaultFormat" ); 441 442 } 443 444// </editor-fold>//GEN-END:jdtausProperties 445 446 //--------------------------------------------------------------Properties-- 447 //--Messages---------------------------------------------------------------- 448 449// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausMessages 450 // This section is managed by jdtaus-container-mojo. 451 452 /** 453 * Gets the text of message <code>blzInfo</code>. 454 * <blockquote><pre>BLZ {0}</pre></blockquote> 455 * <blockquote><pre>BLZ {0}</pre></blockquote> 456 * 457 * @param locale The locale of the message instance to return. 458 * @param bankCode format parameter. 459 * 460 * @return the text of message <code>blzInfo</code>. 461 */ 462 private String getBlzInfoMessage( final Locale locale, 463 final java.lang.String bankCode ) 464 { 465 return ContainerFactory.getContainer(). 466 getMessage( this, "blzInfo", locale, 467 new Object[] 468 { 469 bankCode 470 }); 471 472 } 473 474 /** 475 * Gets the text of message <code>bicInfo</code>. 476 * <blockquote><pre>BIC {0}</pre></blockquote> 477 * <blockquote><pre>BIC {0}</pre></blockquote> 478 * 479 * @param locale The locale of the message instance to return. 480 * @param bic format parameter. 481 * 482 * @return the text of message <code>bicInfo</code>. 483 */ 484 private String getBicInfoMessage( final Locale locale, 485 final java.lang.String bic ) 486 { 487 return ContainerFactory.getContainer(). 488 getMessage( this, "bicInfo", locale, 489 new Object[] 490 { 491 bic 492 }); 493 494 } 495 496 /** 497 * Gets the text of message <code>headOfficeInfo</code>. 498 * <blockquote><pre>Hauptstelle</pre></blockquote> 499 * <blockquote><pre>Headoffice</pre></blockquote> 500 * 501 * @param locale The locale of the message instance to return. 502 * 503 * @return the text of message <code>headOfficeInfo</code>. 504 */ 505 private String getHeadOfficeInfoMessage( final Locale locale ) 506 { 507 return ContainerFactory.getContainer(). 508 getMessage( this, "headOfficeInfo", locale, null ); 509 510 } 511 512 /** 513 * Gets the text of message <code>bankleitzahlMarkedForDeletionInfo</code>. 514 * <blockquote><pre>Vorgesehen zur Löschung am {0,date,full}.</pre></blockquote> 515 * <blockquote><pre>Marked for deletion at {0,date,full}.</pre></blockquote> 516 * 517 * @param locale The locale of the message instance to return. 518 * @param deletionDate format parameter. 519 * 520 * @return the text of message <code>bankleitzahlMarkedForDeletionInfo</code>. 521 */ 522 private String getBankleitzahlMarkedForDeletionInfoMessage( final Locale locale, 523 final java.util.Date deletionDate ) 524 { 525 return ContainerFactory.getContainer(). 526 getMessage( this, "bankleitzahlMarkedForDeletionInfo", locale, 527 new Object[] 528 { 529 deletionDate 530 }); 531 532 } 533 534// </editor-fold>//GEN-END:jdtausMessages 535 536 //----------------------------------------------------------------Messages-- 537}