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( "&nbsp;(" ).append( bankleitzahlInfo.getDescription() ).append( ")" );
372        }
373
374        buf.append( "<br>" ).append( zipFormat.format( bankleitzahlInfo.getPostalCode() ) ).append( "&nbsp;" ).
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&nbsp;{0}</pre></blockquote>
455     * <blockquote><pre>BLZ&nbsp;{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&nbsp;{0}</pre></blockquote>
477     * <blockquote><pre>BIC&nbsp;{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}